before fixing the icon

This commit is contained in:
Faheed 2026-02-01 13:38:06 +03:00
parent 1dcb03a1f0
commit dba9af100a
78 changed files with 14425 additions and 18174 deletions

View File

@ -0,0 +1,25 @@
from django import template
from django.utils.safestring import mark_safe
import uuid
register=template.Library()
@register.inclusion_tag('logo/ats_logo.html')
def ats_tenhal_logo(height="140", extra_classes=""):
"""
Renders the Tenhal Logo.
The width is automatically calculated based on the 500:140 aspect ratio.
"""
# Calculate width based on original aspect ratio (approx 3.57)
try:
width = int(float(height) * (500 / 140))
except ValueError:
width = "auto"
return {
'height': height,
'width': width,
'classes': extra_classes,
# We generate a unique ID so gradients don't break if the logo is used twice on one page
'uid': uuid.uuid4().hex[:6]
}

View File

@ -0,0 +1,142 @@
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag
def logo_tenhal_ats(height="40", width=None):
"""
Renders the Tenhal ATS logo SVG.
Usage: {% logo_tenhal_ats height="40" %}
"""
# Calculate width proportionally if not provided
if width is None:
width = str(int(float(height) * 3.57)) # Aspect ratio of 500/140
svg = f'''
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 140" height="{height}" width="{width}" class="tenhal-logo">
<defs>
<linearGradient id="redGradient-{height}" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#C41E3A;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8B1228;stop-opacity:1" />
</linearGradient>
<filter id="shadow-{height}" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceAlpha" stdDeviation="2"/>
<feOffset dx="0" dy="2" result="offsetblur"/>
<feComponentTransfer>
<feFuncA type="linear" slope="0.15"/>
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g transform="translate(40, 35)" filter="url(#shadow-{height})">
<path d="M 30 5 L 50 15 L 50 35 L 30 45 L 10 35 L 10 15 Z"
fill="url(#redGradient-{height})"
stroke="none"/>
<path d="M 30 15 L 40 20 L 40 30 L 30 35 L 20 30 L 20 20 Z"
fill="none"
stroke="#ffffff"
stroke-width="2"
opacity="0.8"/>
<circle cx="30" cy="23" r="3" fill="#ffffff"/>
<path d="M 24 30 Q 30 27 36 30"
fill="none"
stroke="#ffffff"
stroke-width="2"
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>
<rect x="95" y="35" width="2" height="55" fill="#E0E0E0"/>
<text x="125" y="62"
font-family="'Helvetica Neue', Arial, sans-serif"
font-size="38"
font-weight="300"
fill="#f8f7f2"
letter-spacing="4">TENHAL</text>
<rect x="125" y="72" width="55" height="2.5" fill="#C41E3A"/>
<text x="125" y="92"
font-family="'Helvetica Neue', Arial, sans-serif"
font-size="13"
font-weight="400"
fill="#f8f7f2"
letter-spacing="2.5">APPLICANT TRACKING SYSTEM</text>
</svg>
'''
return mark_safe(svg)
@register.simple_tag
def logo_ats(height="40", width=None):
"""
Renders the Tenhal ATS logo SVG.
Usage: {% logo_tenhal_ats height="40" %}
"""
# Calculate width proportionally if not provided
if width is None:
width = height # Aspect ratio of 500/140
svg = f'''
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 140" height="{height}" width="{width}" class="tenhal-logo">
<defs>
<linearGradient id="redGradient-{height}" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#C41E3A;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8B1228;stop-opacity:1" />
</linearGradient>
<filter id="shadow-{height}" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceAlpha" stdDeviation="2"/>
<feOffset dx="0" dy="2" result="offsetblur"/>
<feComponentTransfer>
<feFuncA type="linear" slope="0.15"/>
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g transform="translate(40, 35)" filter="url(#shadow-{height})">
<path d="M 30 5 L 50 15 L 50 35 L 30 45 L 10 35 L 10 15 Z"
fill="url(#redGradient-{height})"
stroke="none"/>
<path d="M 30 15 L 40 20 L 40 30 L 30 35 L 20 30 L 20 20 Z"
fill="none"
stroke="#ffffff"
stroke-width="2"
opacity="0.8"/>
<circle cx="30" cy="23" r="3" fill="#ffffff"/>
<path d="M 24 30 Q 30 27 36 30"
fill="none"
stroke="#ffffff"
stroke-width="2"
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>
</svg>
'''
return mark_safe(svg)

View File

@ -25,7 +25,8 @@ urlpatterns = [
# Job-specific Views # Job-specific Views
path("jobs/<slug:slug>/applicants/", views.job_applicants_view, name="job_applicants"), path("jobs/<slug:slug>/applicants/", views.job_applicants_view, name="job_applicants"),
path("jobs/<slug:slug>/applications/", views.JobApplicationListView.as_view(), name="job_applications_list"), path("jobs/<slug:slug>/applications/", views.JobApplicationListView.as_view(), name="job_applications_list"),
path("jobs/<slug:slug>/calendar/", views.interview_calendar_view, name="interview_calendar"), path
("jobs/<slug:slug>/calendar/", views.interview_calendar_view, name="interview_calendar"),
# Job Actions & Integrations # Job Actions & Integrations
path("jobs/<slug:slug>/post-to-linkedin/", views.post_to_linkedin, name="post_to_linkedin"), path("jobs/<slug:slug>/post-to-linkedin/", views.post_to_linkedin, name="post_to_linkedin"),

View File

@ -0,0 +1,310 @@
# Template Tailwind CSS Conversion Summary
## Objective
Convert all templates to match the base.html theme and use only Tailwind CSS instead of Bootstrap.
## Base Theme Configuration
- **Primary Color**: Temple Red (#9d2235) and hover (#7a1a29)
- **Background Colors**: White (#ffffff), Light Gray (#f9fafb)
- **Border Colors**: Gray-200 (#e5e7eb)
- **Text Colors**: Gray-900 (#111827), Gray-700 (#374151), Gray-600 (#4b5563)
- **Icons**: Lucide Icons
- **Components**:
- Cards: `bg-white rounded-2xl shadow-sm border border-gray-200`
- Buttons Primary: `bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl`
- Buttons Secondary: `border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl`
- Inputs: `w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent`
## Completed Templates ✓
### Includes Directory (ALL COMPLETED ✓)
- [x] delete_modal.html
- [x] edit_comment_form.html
- [x] delete_comment_form.html
- [x] comment_form.html
- [x] comment_list.html
- [x] meeting_form.html
- [x] _list_view_switcher.html
- [x] language_options.html
- [x] search_form.html
- [x] easy_logs.html
- [x] paginator.html
- [x] application_exam_status_form.html
- [x] application_modal_body.html
- [x] applications_update_exam_form.html
- [x] applications_update_interview_form.html
- [x] applications_update_offer_form.html
- [x] candidate_resume_template.html
- [x] copy_to_clipboard.html
- [x] schedule_interview_div.html
### Recruitment Directory
- [x] application_delete.html
- [x] candidate_delete.html
- [x] agency_access_link_confirm.html
- [x] notification_confirm_all_read.html
- [x] notification_confirm_delete.html
- [x] agency_access_link_detail.html
- [x] agency_portal_assignment_detail.html
- [x] applicant_signup.html
- [x] agency_portal_persons_list.html
- [x] interview_calendar.html
- [x] source_form.html
### Recruitment/Partials Directory
- [x] stage_update_modal.html
- [x] note_form.html
- [x] note_modal.html
- [x] stage_confirmation_modal.html
## Templates Requiring Conversion
### Recruitment Directory
- [ ] agency_form.html
- [ ] agency_detail.html
- [ ] agency_portal_login.html
- [ ] agency_portal_dashboard.html
- [ ] agency_list.html
- [ ] agency_assignment_list.html
- [ ] settings_form.html
- [ ] settings_list.html
- [ ] settings_detail.html
- [ ] application_detail.html
- [ ] applications_list.html
- [ ] applicant_profile.html
- [ ] dashboard.html
### Recruitment/Partials Directory
- [ ] _candidate_table.html
- [ ] _guage_chart.html
- [ ] ai_overview_breadcromb.html
- [ ] error.html
- [ ] exam-results.html
- [ ] interview-results.html
- [ ] offer-results.html
- [ ] stage_display.html
- [ ] stage_update_form.html
- [ ] stage_update_success.html
- [ ] stats_cards.html
### Jobs Directory
- [ ] job_list.html
- [ ] job_detail.html
- [ ] create_job.html
- [ ] edit_job.html
- [ ] job_applicants.html
- [ ] applicants_of_stage.html
- [ ] apply_form.html
- [ ] application_success.html
- [ ] job_bank.html
- [ ] job_applications_list.html
### Jobs/Partials Directory
- [ ] delete_modal.html
- [ ] applicant_tracking.html
- [ ] image_upload.html
- [ ] linkedin_content_form.html
### Account Directory
- [ ] login.html
- [ ] logout.html
- [ ] account_inactive.html
- [ ] password_reset.html
- [ ] password_reset_done.html
- [ ] password_reset_from_key.html
- [ ] password_reset_from_key_done.html
- [ ] password_change.html
- [ ] email_confirm.html
- [ ] email.html
- [ ] verification_sent.html
- [ ] email/email_confirmation_message.html
- [ ] email/password_reset_key_message.html
### Interviews Directory
- [ ] interview_list.html
- [ ] interview_detail.html
- [ ] interview_create_type_selection.html
- [ ] interview_create_onsite.html
- [ ] interview_create_remote.html
- [ ] interview_participants_form.html
- [ ] onsite_location_form.html
- [ ] preview_schedule.html
- [ ] schedule_interviews.html
### Interviews/Partials Directory
- [ ] ai_questions_list.html
- [ ] interview_list.html
### Interviews/Email Directory
- [ ] interview_invitation.html
### People Directory
- [ ] person_list.html
- [ ] person_detail.html
- [ ] create_person.html
- [ ] update_person.html
- [ ] delete_person.html
- [ ] person_confirm_delete.html
### Messages Directory
- [ ] message_list.html
- [ ] message_detail.html
- [ ] message_form.html
- [ ] application_message_list.html
- [ ] application_message_detail.html
- [ ] application_message_form.html
### Meetings Directory
- [ ] list_meetings.html
- [ ] meeting_details.html
- [ ] create_meeting.html
- [ ] update_meeting.html
- [ ] delete_meeting_form.html
- [ ] create_remote_meeting.html
- [ ] reschedule_meeting.html
- [ ] reschedule_onsite_meeting.html
- [ ] schedule_meeting_form.html
- [ ] schedule_onsite_meeting_form.html
### User Directory
- [ ] profile.html
- [ ] settings.html
- [ ] admin_settings.html
- [ ] portal_profile.html
- [ ] staff_password_create.html
- [ ] create_staff.html
### Applicant Directory
- [ ] career.html
- [ ] job_application_detail.html
- [ ] application_submit_form.html
### Applicant/Partials Directory
- [ ] candidate_facing_base.html
### Forms Directory
- [ ] form_list.html
- [ ] form_templates_list.html
- [ ] create_form_template.html
- [ ] edit_form.html
- [ ] form_builder.html
- [ ] form_embed.html
- [ ] form_preview.html
- [ ] form_template_submissions_list.html
- [ ] form_template_all_submissions.html
- [ ] form_submission_details.html
- [ ] document_form.html
## Conversion Guidelines
### Key Changes Made:
1. **Removed Bootstrap classes** (e.g., `btn btn-primary`, `card`, `form-control`)
2. **Added Tailwind CSS classes** with custom color scheme
3. **Replaced Bootstrap icons** with Lucide Icons
4. **Standardized component styling** using:
- `bg-temple-red` for primary actions
- `rounded-2xl` for cards
- `shadow-sm` for subtle depth
- Consistent padding and spacing
### Common Patterns:
#### Buttons
```html
<!-- Primary -->
<button class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
<i data-lucide="icon-name" class="w-4 h-4"></i> Button Text
</button>
<!-- Secondary -->
<button class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl transition">
<i data-lucide="icon-name" class="w-4 h-4"></i> Button Text
</button>
```
#### Cards
```html
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="p-6">
<!-- Content -->
</div>
</div>
```
#### Forms
```html
<div class="space-y-2">
<label for="field-id" class="block text-sm font-semibold text-gray-700 mb-2">
Label Text <span class="text-red-500">*</span>
</label>
<input type="text" id="field-id" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition">
</div>
```
#### Alerts/Notifications
```html
<!-- Success -->
<div class="bg-green-50 border border-green-200 text-green-800 rounded-xl p-4 mb-6 font-semibold text-sm">
Success Message
</div>
<!-- Error -->
<div class="bg-red-50 border border-red-200 text-red-800 rounded-xl p-4 mb-6 font-semibold text-sm">
Error Message
</div>
<!-- Warning -->
<div class="bg-yellow-50 border border-yellow-200 text-yellow-800 rounded-xl p-4 mb-6 font-semibold text-sm">
Warning Message
</div>
<!-- Info -->
<div class="bg-blue-50 border border-blue-200 text-blue-800 rounded-xl p-4 mb-6 font-semibold text-sm">
Info Message
</div>
```
#### Modals
```html
<div class="hidden fixed inset-0 z-50 overflow-y-auto" id="modalId">
<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"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" 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-lg sm:w-full">
<!-- Modal Content -->
</div>
</div>
</div>
```
#### Badges
```html
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-temple-red text-white">
Badge Text
</span>
```
## Status Summary
- **Total Templates**: ~150+
- **Completed**: ~25
- **Remaining**: ~125+
- **Completion**: ~17%
## Next Steps
1. Continue converting remaining templates in recruitment directory
2. Convert all templates in jobs directory
3. Convert all templates in account directory
4. Convert all templates in interviews directory
5. Convert all templates in other directories
6. Remove Bootstrap CDN links from base.html once all templates are converted
7. Test all templates for consistency and functionality
## Notes
- All converted templates use Lucide Icons instead of Font Awesome or Bootstrap icons
- Custom Tailwind config needed for `bg-temple-red` color (should be added to tailwind.config.js)
- Modal interactions now use JavaScript instead of Bootstrap's data attributes
- All forms use consistent styling and validation states
- Responsive design is maintained through Tailwind's responsive prefixes (sm:, md:, lg:, etc.)

View File

@ -145,4 +145,4 @@
lucide.createIcons(); lucide.createIcons();
</script> </script>
</body> </body>
</html> </html>h

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,291 +4,498 @@
{% block title %}{% trans "Application" %}-{{ job.title }}{% endblock %} {% block title %}{% trans "Application" %}-{{ job.title }}{% endblock %}
{% block content %} {% block content %}
<div class="page-wrapper">
<!-- Simple Header -->
<div class="page-header">
<div class="container">
<a href="{% url 'kaauh_career' %}" class="back-link">
<i data-lucide="arrow-left" class="icon-sm"></i>
<span>{% trans "All Positions" %}</span>
</a>
</div>
</div>
<div class="container">
<div class="content-wrapper">
<!-- Main Content -->
<main class="main-content">
<!-- Job Header -->
<div class="job-header">
<h1 class="job-title">{{ job.title }}</h1>
<div class="job-meta">
<span class="meta-item">
<i data-lucide="building-2" class="icon-sm"></i>
{{ job.department }}
</span>
<span class="meta-item">
<i data-lucide="map-pin" class="icon-sm"></i>
{{ job.get_location_display }}
</span>
<span class="meta-item">
<i data-lucide="briefcase" class="icon-sm"></i>
{{ job.get_job_type_display }}
</span>
</div>
</div>
<!-- Job Details Grid -->
<div class="details-grid">
{% if job.salary_range %}
<div class="detail-item">
<div class="detail-label">{% trans "Salary" %}</div>
<div class="detail-value">{{ job.salary_range }}</div>
</div>
{% endif %}
<div class="detail-item">
<div class="detail-label">{% trans "Deadline" %}</div>
<div class="detail-value">
{% if job.application_deadline %}
{{ job.application_deadline|date:"M d, Y" }}
{% else %}
{% trans "Open" %}
{% endif %}
</div>
</div>
<div class="detail-item">
<div class="detail-label">{% trans "Work Mode" %}</div>
<div class="detail-value">{{ job.workplace_type|default:"On-site" }}</div>
</div>
</div>
<!-- Content Sections -->
{% if job.has_description_content %}
<section class="content-section">
<h2 class="section-heading">{% trans "About the Role" %}</h2>
<div class="section-content">
{{ job.description|safe }}
</div>
</section>
{% endif %}
{% if job.has_qualifications_content %}
<section class="content-section">
<h2 class="section-heading">{% trans "What We're Looking For" %}</h2>
<div class="section-content">
{{ job.qualifications|safe }}
</div>
</section>
{% endif %}
</main>
<!-- Sidebar -->
<aside class="sidebar">
<div class="apply-card">
{% if job.form_template %}
{% if user.is_authenticated and already_applied %}
<div class="applied-badge">
<i data-lucide="check-circle" class="icon-md"></i>
<span>{% trans "Applied" %}</span>
</div>
<p class="apply-text">{% trans "Your application has been submitted" %}</p>
{% else %}
<a href="{% url 'application_submit_form' job.slug %}" class="apply-button">
{% trans "Apply for this Position" %}
<i data-lucide="arrow-right" class="icon-sm"></i>
</a>
{% if job.application_deadline %}
<p class="apply-deadline">
{% trans "Apply before" %} {{ job.application_deadline|date:"M d, Y" }}
</p>
{% endif %}
{% endif %}
{% endif %}
</div>
<!-- Quick Info -->
<div class="info-card">
<h3 class="info-title">{% trans "Quick Info" %}</h3>
<div class="info-list">
{% if job.internal_job_id %}
<div class="info-item">
<span class="info-label">{% trans "Job ID" %}</span>
<span class="info-value">{{ job.internal_job_id }}</span>
</div>
{% endif %}
<div class="info-item">
<span class="info-label">{% trans "Posted" %}</span>
<span class="info-value">{{ job.created_at|date:"M d, Y" }}</span>
</div>
</div>
</div>
</aside>
</div>
</div>
</div>
<!-- Mobile Apply -->
{% if job.form_template and not already_applied %}
<div class="mobile-apply">
<a href="{% url 'application_submit_form' job.slug %}" class="mobile-apply-btn">
{% trans "Apply Now" %}
</a>
</div>
{% endif %}
<style> <style>
/* 1. LAYOUT & GRID */ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
.page-container {
max-width: 1200px; :root {
margin: 2rem auto 5rem; --red: #9d2235;
padding-inline: 1.5rem; --red-dark: #7a1a29;
display: grid; --gray-50: #fafafa;
grid-template-columns: 1fr 350px; --gray-100: #f5f5f5;
gap: 2rem; --gray-200: #e5e5e5;
align-items: start; --gray-400: #a3a3a3;
--gray-600: #525252;
--gray-900: #171717;
} }
@media (max-width: 992px) { * {
.page-container { grid-template-columns: 1fr; } margin: 0;
.desktop-sidebar { display: none; } padding: 0;
box-sizing: border-box;
} }
/* 2. STICKY TOP NAV */ body {
.job-nav { font-family: 'Inter', -apple-system, sans-serif;
background-color: var(--temple-red); color: var(--gray-900);
color: white; background: #ffffff;
padding: 0.75rem 1.5rem; line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
/* Layout */
.page-wrapper {
min-height: 100vh;
}
.page-header {
border-bottom: 1px solid var(--gray-200);
padding: 1.5rem 0;
background: #ffffff;
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 100; z-index: 100;
font-weight: 700;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
} }
/* 3. CARDS & SECTIONS */ .container {
.content-card { max-width: 75rem;
background: white; margin: 0 auto;
border-radius: 1rem; padding: 0 1.5rem;
border: 1px solid var(--border);
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
overflow: hidden;
} }
.card-header { .content-wrapper {
padding: 1.5rem;
border-bottom: 1px solid var(--border);
background: white;
}
.card-body { padding: 1.5rem; }
/* 4. METADATA GRID */
.meta-section {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); grid-template-columns: 1fr;
gap: 1.25rem; gap: 3rem;
background: #f9fafb; padding: 3rem 0;
border: 1px solid var(--border); }
border-radius: 0.75rem;
padding: 1.5rem; @media (min-width: 1024px) {
.content-wrapper {
grid-template-columns: 1fr 22rem;
gap: 4rem;
}
}
/* Back Link */
.back-link {
display: inline-flex;
align-items: center;
gap: 0.5rem;
color: var(--gray-600);
font-size: 0.875rem;
font-weight: 500;
text-decoration: none;
transition: color 0.2s;
}
.back-link:hover {
color: var(--red);
}
/* Job Header */
.job-header {
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.job-title {
font-size: 2.5rem;
font-weight: 700;
line-height: 1.2;
margin-bottom: 1rem;
letter-spacing: -0.02em;
}
.job-meta {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
color: var(--gray-600);
font-size: 0.9375rem;
}
.meta-item { .meta-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.5rem;
font-size: 0.9rem;
color: var(--gray-text);
} }
.meta-item svg { /* Details Grid */
width: 1.25rem; .details-grid {
height: 1.25rem; display: grid;
color: var(--temple-red); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
flex-shrink: 0; gap: 1.5rem;
padding: 2rem;
background: var(--gray-50);
border-radius: 0.75rem;
margin-bottom: 3rem;
} }
/* 5. PURE CSS ACCORDION */ .detail-item {
.accordion-item { border-bottom: 1px solid var(--border); }
.accordion-toggle { display: none; }
.accordion-header {
display: flex; display: flex;
align-items: center; flex-direction: column;
justify-content: space-between; gap: 0.25rem;
padding: 1.25rem; }
cursor: pointer;
.detail-label {
font-size: 0.8125rem;
font-weight: 500;
color: var(--gray-600);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.detail-value {
font-size: 1rem;
font-weight: 600;
color: var(--gray-900);
}
/* Content Sections */
.content-section {
margin-bottom: 3rem;
}
.section-heading {
font-size: 1.5rem;
font-weight: 700; font-weight: 700;
color: var(--temple-red-dark); margin-bottom: 1.5rem;
transition: background 0.2s; letter-spacing: -0.01em;
} }
.accordion-header:hover { background: var(--temple-cream); } .section-content {
font-size: 1rem;
.accordion-content { line-height: 1.75;
max-height: 0; color: var(--gray-600);
overflow: hidden;
transition: max-height 0.3s ease-out;
color: #4b5563;
} }
.accordion-toggle:checked + .accordion-header + .accordion-content { .section-content h1,
max-height: 2000px; /* Large enough for content */ .section-content h2,
padding: 1rem 1.25rem 2rem; .section-content h3,
.section-content h4 {
font-weight: 600;
color: var(--gray-900);
margin: 2rem 0 1rem;
} }
.accordion-icon { .section-content h2 { font-size: 1.25rem; }
transition: transform 0.3s; .section-content h3 { font-size: 1.125rem; }
}
.accordion-toggle:checked + .accordion-header .accordion-icon { .section-content p {
transform: rotate(180deg); margin-bottom: 1rem;
} }
/* 6. SIDEBAR & BUTTONS */ .section-content ul,
.sticky-sidebar { .section-content ol {
margin: 1rem 0;
padding-left: 1.5rem;
}
.section-content li {
margin-bottom: 0.5rem;
}
.section-content strong {
font-weight: 600;
color: var(--gray-900);
}
.section-content a {
color: var(--red);
text-decoration: underline;
text-underline-offset: 2px;
}
/* Sidebar */
.sidebar {
position: sticky; position: sticky;
top: 80px; top: 7rem;
align-self: start;
} }
.btn-apply { .apply-card,
.info-card {
background: #ffffff;
border: 1px solid var(--gray-200);
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
/* Apply Button */
.apply-button {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 0.5rem; gap: 0.5rem;
background: var(--temple-red);
color: white;
padding: 1rem;
border-radius: 0.75rem;
font-weight: 700;
text-decoration: none;
width: 100%; width: 100%;
border: none; padding: 1rem 1.5rem;
transition: 0.2s; background: var(--red);
color: #ffffff;
font-weight: 600;
font-size: 1rem;
border-radius: 0.5rem;
text-decoration: none;
transition: background 0.2s;
} }
.btn-apply:disabled { background: #9ca3af; cursor: not-allowed; } .apply-button:hover {
.btn-apply:not(:disabled):hover { background: var(--temple-red-dark); } background: var(--red-dark);
}
/* 7. MOBILE FOOTER */ .apply-text {
.mobile-apply-bar { font-size: 0.875rem;
color: var(--gray-600);
margin-top: 1rem;
text-align: center;
}
.apply-deadline {
font-size: 0.8125rem;
color: var(--gray-600);
margin-top: 0.75rem;
text-align: center;
}
/* Applied Badge */
.applied-badge {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 1rem;
background: var(--gray-50);
border-radius: 0.5rem;
font-weight: 600;
color: var(--gray-600);
}
/* Info Card */
.info-title {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 1rem;
color: var(--gray-900);
}
.info-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.info-item {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.875rem;
}
.info-label {
color: var(--gray-600);
}
.info-value {
font-weight: 500;
color: var(--gray-900);
}
/* Mobile Apply */
.mobile-apply {
display: none;
}
@media (max-width: 1023px) {
.mobile-apply {
display: block;
position: fixed; position: fixed;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
background: white;
padding: 1rem; padding: 1rem;
border-top: 1px solid var(--border); background: #ffffff;
box-shadow: 0 -4px 6px -1px rgba(0,0,0,0.1); border-top: 1px solid var(--gray-200);
z-index: 1000; z-index: 100;
} }
@media (min-width: 993px) { .mobile-apply-bar { display: none; } } .mobile-apply-btn {
display: block;
width: 100%;
padding: 1rem;
background: var(--red);
color: #ffffff;
font-weight: 600;
text-align: center;
border-radius: 0.5rem;
text-decoration: none;
}
.sidebar {
position: static;
}
body {
padding-bottom: 5rem;
}
}
/* Icons */
.icon-sm {
width: 1.125rem;
height: 1.125rem;
}
.icon-md {
width: 1.25rem;
height: 1.25rem;
}
/* Mobile Responsive */
@media (max-width: 768px) {
.job-title {
font-size: 2rem;
}
.details-grid {
grid-template-columns: 1fr;
padding: 1.5rem;
gap: 1.25rem;
}
.content-wrapper {
padding: 2rem 0;
}
}
</style> </style>
<nav class="job-nav"> <script>
<div style="max-width: 1200px; margin: 0 auto;"> document.addEventListener('DOMContentLoaded', function() {
{% trans "Job Overview" %} if (typeof lucide !== 'undefined') {
</div> lucide.createIcons();
</nav> }
});
<div class="page-container"> </script>
<main>
<article class="content-card">
<header class="card-header">
<h1 style="font-size: 1.875rem; font-weight: 800; color: var(--temple-red-dark); margin: 0;">
{{ job.title }}
</h1>
</header>
<div class="card-body">
<h4 style="font-size: 1.1rem; font-weight: 700; color: #6b7280; border-bottom: 2px solid #f3f4f6; padding-bottom: 0.5rem; margin-bottom: 1.5rem;">
{% trans "Summary" %}
</h4>
<section class="meta-section">
{% if job.salary_range %}
<div class="meta-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<strong>{% trans "Salary:" %}</strong> <span style="color: var(--temple-red); font-weight: 700;">{{ job.salary_range }}</span>
</div>
{% endif %}
<div class="meta-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
<strong>{% trans "Deadline:" %}</strong>
{% if job.application_deadline %}
{{ job.application_deadline|date:"M d, Y" }}
{% if job.is_expired %}<span style="color: #dc2626; font-size: 0.7rem; font-weight: 800;">(EXPIRED)</span>{% endif %}
{% else %}{% trans "Ongoing" %}{% endif %}
</div>
<div class="meta-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg>
<strong>{% trans "Type:" %}</strong> {{ job.get_job_type_display }}
</div>
<div class="meta-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path></svg>
<strong>{% trans "Location:" %}</strong> {{ job.get_location_display }}
</div>
<div class="meta-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path></svg>
<strong>{% trans "Department:" %}</strong> {{ job.department|default:"N/A" }}
</div>
<div class="meta-item">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"></path></svg>
<strong>{% trans "ID:" %}</strong> {{ job.internal_job_id|default:"N/A" }}
</div>
</section>
<div class="accordion-container">
{% if job.has_description_content %}
<div class="accordion-item">
<input type="checkbox" id="acc1" class="accordion-toggle" checked>
<label for="acc1" class="accordion-header">
<span style="display: flex; align-items: center; gap: 0.75rem;">
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
{% trans "Job Description" %}
</span>
<svg class="accordion-icon heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
</label>
<div class="accordion-content">
<div class="wysiwyg-content">{{ job.description|safe }}</div>
</div>
</div>
{% endif %}
{% if job.has_qualifications_content %}
<div class="accordion-item">
<input type="checkbox" id="acc2" class="accordion-toggle">
<label for="acc2" class="accordion-header">
<span style="display: flex; align-items: center; gap: 0.75rem;">
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"></path></svg>
{% trans "Qualifications" %}
</span>
<svg class="accordion-icon heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
</label>
<div class="accordion-content">
<div class="wysiwyg-content">{{ job.qualifications|safe }}</div>
</div>
</div>
{% endif %}
</div>
</div>
</article>
</main>
<aside class="desktop-sidebar">
<div class="content-card sticky-sidebar">
<header class="card-header">
<h5 style="margin: 0; font-weight: 800; color: var(--temple-red); display: flex; align-items: center; gap: 0.5rem;">
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
{% trans "Ready to Apply?" %}
</h5>
</header>
<div class="card-body" style="text-align: center;">
<p style="color: #6b7280; font-size: 0.875rem; margin-bottom: 1.5rem;">
{% trans "Review the details before submitting your application." %}
</p>
{% if job.form_template %}
{% if user.is_authenticated and already_applied %}
<button class="btn-apply" disabled>
{% trans "Already Applied" %}
</button>
{% else %}
<a href="{% url 'application_submit_form' job.slug %}" class="btn-apply">
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
{% trans "Apply Now" %}
</a>
{% endif %}
{% endif %}
</div>
</div>
</aside>
</div>
{% if job.form_template %}
<div class="mobile-apply-bar">
{% if user.is_authenticated and already_applied %}
<button class="btn-apply" disabled style="width: 100%;">{% trans "Already Applied" %}</button>
{% else %}
<a href="{% url 'application_submit_form' job.slug %}" class="btn-apply">{% trans "Apply for this Position" %}</a>
{% endif %}
</div>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -9,270 +9,89 @@
<title>{% trans "Careers" %} - {% block title %}{% trans "Application Form" %}{% endblock %}</title> <title>{% trans "Careers" %} - {% block title %}{% trans "Application Form" %}{% endblock %}</title>
<link rel="icon" type="image/png" href="{% static 'image/favicon/favicon-32x32.png'%}"> <link rel="icon" type="image/png" href="{% static 'image/favicon/favicon-32x32.png'%}">
<script src="https://cdn.tailwindcss.com"></script>
<style> <script>
:root { tailwind.config = {
--temple-red: #9d2235; theme: {
--temple-red-dark: #7a1a29; extend: {
--temple-dark: #1a1a1a; colors: {
--temple-cream: #f8f7f2; 'temple-red': '#9d2235',
--white: #ffffff; 'temple-red-dark': '#7a1a29',
--light-bg: #f8f9fa; 'temple-cream': '#f8f7f2',
--gray-text: #6c757d;
--danger: #dc3545;
--border: #d0d7de;
--shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
--nav-height: 70px;
} }
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background-color: var(--light-bg);
color: var(--temple-dark);
line-height: 1.5;
} }
/* --- NAVIGATION --- */
.navbar {
height: var(--nav-height);
background: var(--white);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
padding-inline: 2rem;
position: sticky;
top: 0;
z-index: 1000;
justify-content: space-between;
} }
.nav-brand {
display: flex;
align-items: center;
text-decoration: none;
gap: 0.75rem;
font-weight: 700;
color: var(--temple-red);
font-size: 1.25rem;
} }
</script>
.nav-brand-icon {
width: 45px;
height: 45px;
background: var(--temple-red);
color: white;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: 800;
}
.nav-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.nav-link {
text-decoration: none;
color: var(--gray-text);
font-weight: 500;
transition: color 0.2s;
}
.nav-link:hover { color: var(--temple-red); }
/* --- BUTTONS --- */
.btn-lang {
background: transparent;
border: 1px solid var(--border);
padding: 0.5rem 1rem;
border-radius: 0.5rem;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 500;
color: var(--temple-red);
transition: all 0.2s;
}
.btn-lang:hover { background: var(--temple-cream); }
/* --- DROPDOWN --- */
.dropdown { position: relative; }
.dropdown-trigger {
background: none;
border: none;
cursor: pointer;
display: flex;
align-items: center;
}
.profile-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--temple-cream);
}
.profile-avatar-placeholder {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--temple-red);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.dropdown-menu {
display: none;
position: absolute;
top: calc(100% + 10px);
inset-inline-end: 0;
background: var(--white);
min-width: 260px;
border-radius: 0.75rem;
box-shadow: var(--shadow);
border: 1px solid var(--border);
overflow: hidden;
z-index: 1100;
}
.dropdown.active .dropdown-menu { display: block; }
.dropdown-header {
padding: 1rem;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 0.75rem;
}
.dropdown-item {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
text-decoration: none;
color: #444;
gap: 0.75rem;
transition: background 0.2s;
border: none;
background: none;
width: 100%;
text-align: inherit;
cursor: pointer;
font-size: 0.95rem;
}
.dropdown-item:hover { background: var(--temple-cream); }
.dropdown-item svg { width: 20px; height: 20px; color: var(--gray-text); }
.dropdown-item.logout { color: var(--danger); }
.dropdown-item.logout svg { color: var(--danger); }
/* --- ALERTS --- */
.alert-container {
max-width: 1200px;
margin: 1rem auto;
padding-inline: 1rem;
}
.alert {
padding: 1rem;
border-radius: 0.5rem;
background: #fef2f2;
color: var(--temple-red-dark);
border-inline-start: 4px solid var(--temple-red);
margin-bottom: 0.75rem;
display: flex;
align-items: center;
justify-content: space-between;
}
.heroicon { width: 20px; height: 20px; flex-shrink: 0; }
/* --- RESPONSIVE --- */
@media (max-width: 768px) {
.navbar { padding-inline: 1rem; }
.nav-brand span { display: none; }
.d-mobile-none { display: none; }
}
</style>
</head> </head>
<body> <body class="font-sans bg-gray-50 text-gray-900">
<nav class="navbar"> <!-- Navigation -->
<a href="{% url 'kaauh_career' %}" class="nav-brand"> <nav class="h-[70px] bg-white border-b border-gray-200 flex items-center px-4 md:px-8 sticky top-0 z-50 justify-between">
<div class="nav-brand-icon"> <a href="{% url 'kaauh_career' %}" class="flex items-center gap-3 font-bold text-temple-red text-xl no-underline">
<div class="w-11 h-11 bg-temple-red text-white rounded-lg flex items-center justify-center text-2xl font-extrabold">
<svg style="width:24px; height:24px;" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg style="width:24px; height:24px;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
</svg> </svg>
</div> </div>
<span>{% trans "Careers" %}</span> <span class="hidden md:inline">{% trans "Careers" %}</span>
</a> </a>
<div class="nav-actions"> <div class="flex items-center gap-4">
{% if request.resolver_match.url_name != "kaauh_career" %} {% if request.resolver_match.url_name != "kaauh_career" %}
<a href="{% url 'kaauh_career' %}" class="nav-link d-mobile-none">{% trans "Careers" %}</a> <a href="{% url 'kaauh_career' %}" class="text-gray-500 font-medium transition hover:text-temple-red no-underline hidden md:block">
{% trans "Careers" %}
</a>
{% endif %} {% endif %}
<form action="{% url 'set_language' %}" method="post"> <form action="{% url 'set_language' %}" method="post">
{% csrf_token %} {% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}"> <input name="next" type="hidden" value="{{ request.get_full_path }}">
{% if LANGUAGE_CODE == 'en' %} {% if LANGUAGE_CODE == 'en' %}
<button name="language" value="ar" class="btn-lang" type="submit"> <button name="language" value="ar" class="bg-transparent border border-gray-300 px-4 py-2 rounded-lg cursor-pointer flex items-center gap-2 font-medium text-temple-red transition hover:bg-temple-cream" type="submit">
🇸🇦 <span class="d-mobile-none">العربية</span> 🇸🇦 <span class="hidden md:inline">العربية</span>
</button> </button>
{% else %} {% else %}
<button name="language" value="en" class="btn-lang" type="submit"> <button name="language" value="en" class="bg-transparent border border-gray-300 px-4 py-2 rounded-lg cursor-pointer flex items-center gap-2 font-medium text-temple-red transition hover:bg-temple-cream" type="submit">
🇺🇸 <span class="d-mobile-none">English</span> 🇺🇸 <span class="hidden md:inline">English</span>
</button> </button>
{% endif %} {% endif %}
</form> </form>
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<div class="dropdown" id="userDropdown"> <div class="dropdown relative" id="userDropdown">
<button class="dropdown-trigger" onclick="toggleDropdown()"> <button class="bg-none border-none cursor-pointer flex items-center" onclick="toggleDropdown()">
{% if user.profile_image %} {% if user.profile_image %}
<img src="{{ user.profile_image.url }}" class="profile-avatar"> <img src="{{ user.profile_image.url }}" class="w-10 h-10 rounded-full object-cover border-2 border-temple-cream">
{% else %} {% else %}
<div class="profile-avatar-placeholder"> <div class="w-10 h-10 rounded-full bg-temple-red text-white flex items-center justify-center font-bold">
{{ user.username|first|upper }} {{ user.username|first|upper }}
</div> </div>
{% endif %} {% endif %}
</button> </button>
<div class="dropdown-menu"> <div class="dropdown-menu absolute top-[50px] end-0 bg-white min-w-[260px] rounded-xl shadow-lg border border-gray-200 overflow-hidden z-[60] hidden">
<div class="dropdown-header"> <div class="p-4 border-b border-gray-200 flex items-center gap-3">
<div> <div>
<div style="font-weight: 600;">{{ user.get_full_name|default:user.username }}</div> <div class="font-semibold text-gray-900">{{ user.get_full_name|default:user.username }}</div>
<div style="font-size: 0.8rem; color: var(--gray-text);">{{ user.email|truncatechars:22 }}</div> <div class="text-sm text-gray-500">{{ user.email|truncatechars:22 }}</div>
</div> </div>
</div> </div>
<a href="{% url 'applicant_portal_dashboard' %}" class="dropdown-item"> <a href="{% url 'applicant_portal_dashboard' %}" class="flex items-center p-3 text-gray-700 gap-3 transition hover:bg-temple-cream border-none bg-none w-full text-left cursor-pointer text-base no-underline">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg> <svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
{% trans "Dashboard" %} {% trans "Dashboard" %}
</a> </a>
<a href="{% url 'user_detail' request.user.pk %}" class="dropdown-item"> <a href="{% url 'user_detail' request.user.pk %}" class="flex items-center p-3 text-gray-700 gap-3 transition hover:bg-temple-cream border-none bg-none w-full text-left cursor-pointer text-base no-underline">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
{% trans "My Profile" %} {% trans "My Profile" %}
</a> </a>
<form method="post" action="{% url 'account_logout'%}"> <form method="post" action="{% url 'account_logout'%}">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="dropdown-item logout"> <button type="submit" class="flex items-center p-3 text-red-500 gap-3 transition hover:bg-temple-cream border-none bg-none w-full text-left cursor-pointer text-base">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path></svg> <svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path></svg>
{% trans "Sign Out" %} {% trans "Sign Out" %}
</button> </button>
</form> </form>
@ -282,15 +101,16 @@
</div> </div>
</nav> </nav>
<!-- Alert Messages -->
{% if messages %} {% if messages %}
<div class="alert-container"> <div class="max-w-5xl mx-auto my-4 px-4">
{% for message in messages %} {% for message in messages %}
<div class="alert"> <div class="p-4 rounded-lg bg-red-50 text-temple-red-dark border-l-4 border-temple-red mb-3 flex items-center justify-between">
<div style="display: flex; align-items: center; gap: 0.75rem;"> <div class="flex items-center gap-3">
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> <svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
{{ message }} {{ message }}
</div> </div>
<button onclick="this.parentElement.remove()" style="background:none; border:none; cursor:pointer; font-size: 1.25rem;">&times;</button> <button onclick="this.parentElement.remove()" class="bg-none border-none cursor-pointer text-xl">&times;</button>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@ -300,6 +120,10 @@
{% block content %}{% endblock %} {% block content %}{% endblock %}
</main> </main>
<style>
.dropdown.active .dropdown-menu { display: block; }
</style>
<script> <script>
function toggleDropdown() { function toggleDropdown() {
document.getElementById('userDropdown').classList.toggle('active'); document.getElementById('userDropdown').classList.toggle('active');

View File

@ -1,4 +1,5 @@
{% load static i18n %} {% load static i18n %}
{% load logo_tags %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
<!DOCTYPE html> <!DOCTYPE html>
@ -93,7 +94,7 @@
} }
/* Safe area padding for notched devices */ /* Safe area padding for notched devices */
@supports (padding: env(safe-area-inset-bottom)) { @supports (padding: env(dQrgH6E6C$$!@9safe-area-inset-bottom)) {
.safe-area-padding-bottom { .safe-area-padding-bottom {
padding-bottom: calc(1.5rem + env(safe-area-inset-bottom)); padding-bottom: calc(1.5rem + env(safe-area-inset-bottom));
} }
@ -272,10 +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">
<div class="bg-temple-red p-2 rounded-lg shrink-0"> {% comment %} <div class="bg-temple-red p-2 rounded-lg shrink-0">
<i data-lucide="shield-check" class="w-5 h-5 sm:w-6 sm:h-6 text-white"></i> <i data-lucide="shield-check" class="w-5 h-5 sm:w-6 sm:h-6 text-white"></i>
</div> </div>
<span class="text-lg sm:text-xl font-bold tracking-tight sidebar-text">{% trans "ATS" %}</span> <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' %}">

View File

@ -4,156 +4,40 @@
{% block title %}Create Form Template - ATS{% endblock %} {% block title %}Create Form Template - ATS{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
background-color: white;
}
/* Card Header Theming */
.card-header {
background-color: #f0f8ff !important; /* Light blue tint for header */
border-bottom: 1px solid var(--kaauh-border);
color: var(--kaauh-teal-dark);
font-weight: 600;
padding: 1rem 1.25rem;
border-radius: 0.75rem 0.75rem 0 0;
}
.card-header h3 {
color: var(--kaauh-teal-dark);
font-weight: 700;
}
.card-header .fas {
color: var(--kaauh-teal);
}
/* Form styling */
.form-control {
border-color: var(--kaauh-border);
border-radius: 0.5rem;
}
.form-control:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
}
.form-label {
font-weight: 500;
color: var(--kaauh-primary-text);
margin-bottom: 0.5rem;
}
.form-check-input:checked {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
}
/* Modal styling */
.modal-content {
border-radius: 0.75rem;
border: 1px solid var(--kaauh-border);
}
.modal-header {
border-bottom: 1px solid var(--kaauh-border);
padding: 1.25rem 1.5rem;
background-color: #f0f8ff !important;
}
.modal-footer {
border-top: 1px solid var(--kaauh-border);
padding: 1rem 1.5rem;
}
/* Error message styling */
.invalid-feedback {
display: block;
width: 100%;
margin-top: 0.25rem;
font-size: 0.875em;
color: #dc3545;
}
.form-control.is-invalid {
border-color: #dc3545;
}
/* Success message styling */
.alert-success {
background-color: #d4edda;
border-color: #c3e6cb;
color: #155724;
padding: 1rem 1.25rem;
border-radius: 0.5rem;
margin-bottom: 1rem;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container py-4"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="d-flex justify-content-between align-items-center mb-4 pb-2 border-bottom border-primary"> <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
<h1 class="h3 mb-0 fw-bold text-primary"> <div class="flex-1">
<i class="fas fa-file-alt me-2"></i>Create Form Template <h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
<div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="file-plus-2" class="w-8 h-8 text-temple-red"></i>
</div>
Create Form Template
</h1> </h1>
<a href="{% url 'form_templates_list' %}" class="btn btn-secondary btn-sm"> </div>
<i class="fas fa-arrow-left me-1"></i>Back to Templates <a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i> Back to Templates
</a> </a>
</div> </div>
<div class="row justify-content-center"> <div class="flex justify-center">
<div class="col-lg-8"> <div class="w-full max-w-4xl">
<div class="card"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="card-header"> <div class="bg-gradient-to-br from-temple-red/5 to-transparent border-b border-gray-100 px-6 py-4">
<h3 class="h5 mb-0"><i class="fas fa-plus-circle me-2"></i>New Form Template</h3> <h3 class="text-lg font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="plus-circle" class="w-5 h-5 text-temple-red"></i> New Form Template
</h3>
</div> </div>
<div class="card-body p-4"> <div class="p-6">
<form method="post" id="createFormTemplate"> <form method="post" id="createFormTemplate" class="space-y-6">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
<div class="d-flex justify-content-between"> <div class="flex flex-col sm:flex-row justify-between gap-3 pt-4 border-t border-gray-200">
<a href="{% url 'form_templates_list' %}" class="btn btn-secondary"> <a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-6 py-2.5 rounded-xl text-sm font-medium transition">
<i class="fas fa-times me-1"></i>Cancel <i data-lucide="x" class="w-4 h-4"></i> Cancel
</a> </a>
<button type="submit" class="btn btn-main-action"> <button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
<i class="fas fa-save me-1"></i>Create Template <i data-lucide="save" class="w-4 h-4"></i> Create Template
</button> </button>
</div> </div>
</form> </form>
@ -165,9 +49,9 @@
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="alert alert-success alert-dismissible fade show" role="alert"> <div class="fixed top-4 right-4 z-50 bg-emerald-100 border border-emerald-200 text-emerald-800 px-4 py-3 rounded-xl shadow-lg flex items-center gap-3 animate-in slide-in-from-right">
<i class="fas fa-check-circle me-2"></i>{{ message }} <i data-lucide="check-circle" class="w-5 h-5 text-emerald-600"></i>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> <span class="text-sm font-medium">{{ message }}</span>
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@ -175,7 +59,6 @@
{% block extra_js %} {% block extra_js %}
<script> <script>
// Add form validation
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('createFormTemplate'); const form = document.getElementById('createFormTemplate');
@ -203,6 +86,8 @@
this.classList.remove('is-invalid'); this.classList.remove('is-invalid');
} }
}); });
lucide.createIcons();
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -4,7 +4,7 @@
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<label for="documentType" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Document Type" %}</label> <label for="documentType" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Document Type" %}</label>
<select class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition" id="documentType" name="document_type" required> <select class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 bg-white focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" id="documentType" name="document_type" required>
<option value="">{% trans "Select document type" %}</option> <option value="">{% trans "Select document type" %}</option>
<option value="resume">{% trans "Resume" %}</option> <option value="resume">{% trans "Resume" %}</option>
<option value="cover_letter">{% trans "Cover Letter" %}</option> <option value="cover_letter">{% trans "Cover Letter" %}</option>
@ -17,13 +17,13 @@
<div> <div>
<label for="documentDescription" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Description" %}</label> <label for="documentDescription" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Description" %}</label>
<textarea class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition resize-none" id="documentDescription" name="description" rows="3" placeholder="{% trans 'Optional description...' %}"></textarea> <textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition resize-none" id="documentDescription" name="description" rows="3" placeholder="{% trans 'Optional description...' %}"></textarea>
</div> </div>
<div> <div>
<label for="documentFile" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Choose File" %}</label> <label for="documentFile" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Choose File" %}</label>
<div class="relative"> <div class="relative">
<input type="file" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition file:mr-4 file:py-1.5 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-kaauh-blue file:text-white hover:file:bg-[#004f57]" id="documentFile" name="file" accept=".pdf,.doc,.docx,.jpg,.png" required> <input type="file" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition file:mr-4 file:py-1.5 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-temple-red file:text-white hover:file:bg-[#7a1a29]" id="documentFile" name="file" accept=".pdf,.doc,.docx,.jpg,.png" required>
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none"> <div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
<i data-lucide="upload" class="w-5 h-5 text-gray-400"></i> <i data-lucide="upload" class="w-5 h-5 text-gray-400"></i>
</div> </div>
@ -34,17 +34,13 @@
<div class="mt-6 pt-4 border-t border-gray-200"> <div class="mt-6 pt-4 border-t border-gray-200">
<button type="button" <button type="button"
onclick="this.closest('form').parentElement.parentElement.classList.add('hidden')" class="modal-cancel-btn inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2.5 rounded-xl text-sm transition"
class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2.5 rounded-xl text-sm transition"> data-modal="documentUploadModal">
{% trans "Cancel" %} {% trans "Cancel" %}
</button> </button>
<button type="submit" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md ml-2"> <button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md ml-2">
<i data-lucide="upload" class="w-4 h-4"></i> <i data-lucide="upload" class="w-4 h-4"></i>
{% trans "Upload" %} {% trans "Upload" %}
</button> </button>
</div> </div>
</form> </form>
<script>
lucide.createIcons();
</script>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,85 +5,56 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ form.title }} - Embed</title> <title>{{ form.title }} - Embed</title>
<!-- Bootstrap CSS --> <!-- Tailwind CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome --> <script src="https://unpkg.com/lucide@latest"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'temple-red': '#9d2235',
'temple-dark': '#1a1a1a',
'temple-cream': '#f8f7f2',
}
}
}
}
</script>
<style> <style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
body { body {
background: #f8f9fa; font-family: 'Inter', sans-serif;
margin: 0; -webkit-font-smoothing: antialiased;
padding: 0; -moz-osx-font-smoothing: grayscale;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.embed-container {
background: #f8f9fa;
padding: 0;
min-height: 100vh;
}
.embed-form-wrapper {
margin: 0;
padding: 20px;
}
.embed-form {
max-width: 800px;
margin: 0 auto;
}
.embed-form .card {
border: none;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.embed-form .card-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
padding: 30px;
color: white;
}
.embed-form .card-body {
padding: 0;
}
.embed-info {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 30px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
} }
.code-block { .code-block {
background: #2d3748; background: #2d3748;
color: #e2e8f0; color: #e2e8f0;
border-radius: 8px; border-radius: 0.5rem;
padding: 20px; padding: 1.25rem;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px; font-size: 0.875rem;
line-height: 1.5; line-height: 1.5;
overflow-x: auto; overflow-x: auto;
position: relative; position: relative;
margin-top: 15px; margin-top: 0.875rem;
} }
.copy-btn { .copy-btn {
position: absolute; position: absolute;
top: 10px; top: 0.625rem;
right: 10px; right: 0.625rem;
background: #4a5568; background: #4a5568;
border: none; border: none;
color: white; color: white;
padding: 8px 12px; padding: 0.5rem 0.75rem;
border-radius: 6px; border-radius: 0.375rem;
cursor: pointer; cursor: pointer;
font-size: 12px; font-size: 0.75rem;
transition: background 0.2s; transition: background 0.2s;
} }
@ -94,170 +65,81 @@
.copy-btn.copied { .copy-btn.copied {
background: #48bb78; background: #48bb78;
} }
.preview-iframe {
border: 1px solid #e2e8f0;
border-radius: 8px;
width: 100%;
height: 600px;
background: white;
}
.tab-content {
margin-top: 20px;
}
.nav-tabs .nav-link {
border: none;
background: #f7fafc;
color: #4a5568;
padding: 12px 24px;
margin-right: 8px;
border-radius: 8px 8px 0 0;
transition: all 0.2s;
}
.nav-tabs .nav-link.active {
background: white;
color: #2d3748;
border-bottom: 2px solid #667eea;
}
.nav-tabs .nav-link:hover {
background: #edf2f7;
}
.feature-list {
list-style: none;
padding: 0;
}
.feature-list li {
padding: 8px 0;
border-bottom: 1px solid #e2e8f0;
}
.feature-list li:last-child {
border-bottom: none;
}
.feature-list i {
color: #48bb78;
margin-right: 10px;
}
.dimensions-info {
background: #f7fafc;
border-radius: 8px;
padding: 15px;
margin-top: 15px;
}
.responsive-options {
margin-top: 20px;
}
.responsive-option {
background: white;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
cursor: pointer;
transition: all 0.2s;
}
.responsive-option:hover {
border-color: #667eea;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
}
.responsive-option.active {
border-color: #667eea;
background: #f0f4ff;
}
</style> </style>
</head> </head>
<body> <body class="bg-gray-100 text-gray-800 min-h-screen">
<div class="embed-container"> <div class="p-4 md:p-6">
<div class="embed-form-wrapper"> <div class="max-w-5xl mx-auto">
<div class="embed-form">
<!-- Header --> <!-- Header -->
<div class="embed-info"> <div class="bg-white rounded-2xl shadow-md p-6 mb-6">
<div class="d-flex justify-content-between align-items-start mb-4"> <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
<div> <div class="flex-1">
<h1 class="h2 mb-2"> <h1 class="text-2xl md:text-3xl font-bold text-gray-900 mb-2 flex items-center gap-2">
<i class="fas fa-code text-primary"></i> Embed Form <i data-lucide="code-2" class="w-7 h-7 text-temple-red"></i>
Embed Form
</h1> </h1>
<p class="text-muted mb-0">Get the embed code for "{{ form.title }}"</p> <p class="text-gray-600">Get embed code for "{{ form.title }}"</p>
</div> </div>
<a href="{% url 'form_preview' form.id %}" target="_blank" class="btn btn-outline-primary"> <a href="{% url 'form_preview' form.id %}" target="_blank" class="inline-flex items-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-4 py-2.5 rounded-xl transition font-medium">
<i class="fas fa-external-link-alt"></i> Preview Form <i data-lucide="external-link" class="w-4 h-4"></i> Preview Form
</a> </a>
</div> </div>
<!-- Stats --> <!-- Stats -->
<div class="row text-center mb-4"> <div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-center mb-6">
<div class="col-md-3"> <div class="p-4 bg-gray-50 rounded-xl">
<div class="p-3"> <div class="text-2xl font-bold text-temple-red">{{ form.submissions.count }}</div>
<h4 class="text-primary mb-1">{{ form.submissions.count }}</h4> <div class="text-xs uppercase text-gray-500 font-semibold mt-1">Submissions</div>
<small class="text-muted">Submissions</small>
</div> </div>
<div class="p-4 bg-gray-50 rounded-xl">
<div class="text-2xl font-bold text-emerald-600">{{ form.structure.wizards|length|default:0 }}</div>
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Steps</div>
</div> </div>
<div class="col-md-3"> <div class="p-4 bg-gray-50 rounded-xl">
<div class="p-3"> <div class="text-2xl font-bold text-blue-600">
<h4 class="text-success mb-1">{{ form.structure.wizards|length|default:0 }}</h4>
<small class="text-muted">Steps</small>
</div>
</div>
<div class="col-md-3">
<div class="p-3">
<h4 class="text-info mb-1">
{% if form.structure.wizards %} {% if form.structure.wizards %}
{{ form.structure.wizards.0.fields|length|default:0 }} {{ form.structure.wizards.0.fields|length|default:0 }}
{% else %} {% else %}
0 0
{% endif %} {% endif %}
</h4>
<small class="text-muted">Fields</small>
</div> </div>
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Fields</div>
</div> </div>
<div class="col-md-3"> <div class="p-4 bg-gray-50 rounded-xl">
<div class="p-3"> <div class="text-2xl font-bold text-amber-600">{{ form.created_at|date:"M d" }}</div>
<h4 class="text-warning mb-1">{{ form.created_at|date:"M d" }}</h4> <div class="text-xs uppercase text-gray-500 font-semibold mt-1">Created</div>
<small class="text-muted">Created</small>
</div>
</div> </div>
</div> </div>
<!-- Tabs --> <!-- Tabs -->
<ul class="nav nav-tabs" id="embedTabs" role="tablist"> <div class="flex border-b border-gray-200 mb-6">
<li class="nav-item" role="presentation"> <button class="tab-btn px-6 py-3 font-medium text-gray-600 hover:text-temple-red border-b-2 border-transparent hover:border-temple-red transition flex items-center gap-2 active"
<button class="nav-link active" id="iframe-tab" data-bs-toggle="tab" data-bs-target="#iframe" type="button" role="tab"> data-tab="iframe"
<i class="fas fa-globe"></i> iFrame onclick="switchTab('iframe')">
<i data-lucide="globe" class="w-4 h-4"></i> iFrame
</button> </button>
</li> <button class="tab-btn px-6 py-3 font-medium text-gray-600 hover:text-temple-red border-b-2 border-transparent hover:border-temple-red transition flex items-center gap-2"
<li class="nav-item" role="presentation"> data-tab="popup"
<button class="nav-link" id="popup-tab" data-bs-toggle="tab" data-bs-target="#popup" type="button" role="tab"> onclick="switchTab('popup')">
<i class="fas fa-external-link-alt"></i> Popup <i data-lucide="external-link" class="w-4 h-4"></i> Popup
</button> </button>
</li> <button class="tab-btn px-6 py-3 font-medium text-gray-600 hover:text-temple-red border-b-2 border-transparent hover:border-temple-red transition flex items-center gap-2"
<li class="nav-item" role="presentation"> data-tab="inline"
<button class="nav-link" id="inline-tab" data-bs-toggle="tab" data-bs-target="#inline" type="button" role="tab"> onclick="switchTab('inline')">
<i class="fas fa-code"></i> Inline <i data-lucide="code" class="w-4 h-4"></i> Inline
</button> </button>
</li> </div>
</ul>
<div class="tab-content" id="embedTabsContent"> <!-- Tab Contents -->
<div id="tab-content">
<!-- iFrame Tab --> <!-- iFrame Tab -->
<div class="tab-pane fade show active" id="iframe" role="tabpanel"> <div class="tab-pane" id="iframe-pane">
<h5 class="mb-3">iFrame Embed Code</h5> <h5 class="text-lg font-semibold text-gray-900 mb-3">iFrame Embed Code</h5>
<p class="text-muted">Embed this form directly into your website using an iframe.</p> <p class="text-gray-600 mb-4">Embed this form directly into your website using an iframe.</p>
<div class="code-block"> <div class="code-block">
<button class="copy-btn" onclick="copyToClipboard(this, 'iframe-code')"> <button class="copy-btn" onclick="copyToClipboard(this, 'iframe-code')">
<i class="fas fa-copy"></i> Copy <i data-lucide="copy" class="w-3 h-3"></i> Copy
</button> </button>
<code id="iframe-code"><iframe src="{{ request.build_absolute_uri }}{% url 'form_preview' form.id %}?embed=true" <code id="iframe-code"><iframe src="{{ request.build_absolute_uri }}{% url 'form_preview' form.id %}?embed=true"
width="100%" width="100%"
@ -266,35 +148,38 @@
style="border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);"></iframe></code> style="border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);"></iframe></code>
</div> </div>
<div class="dimensions-info"> <div class="bg-gray-50 rounded-xl p-4 mt-4">
<h6 class="mb-3">Responsive Options</h6> <h6 class="font-semibold text-gray-900 mb-3">Responsive Options</h6>
<div class="responsive-options"> <div class="space-y-2">
<div class="responsive-option active" onclick="selectResponsive(this, 'iframe', 'fixed')"> <div class="responsive-option active bg-white border-2 border-temple-red p-4 rounded-xl cursor-pointer hover:shadow-md transition"
<strong>Fixed Height:</strong> 600px onclick="selectResponsive(this, 'iframe', 'fixed')">
<div class="text-muted small">Best for most websites</div> <div class="font-semibold">Fixed Height: 600px</div>
<div class="text-sm text-gray-500">Best for most websites</div>
</div> </div>
<div class="responsive-option" onclick="selectResponsive(this, 'iframe', 'responsive')"> <div class="responsive-option bg-white border-2 border-gray-200 p-4 rounded-xl cursor-pointer hover:border-temple-red transition"
<strong>Responsive:</strong> Auto height onclick="selectResponsive(this, 'iframe', 'responsive')">
<div class="text-muted small">Adjusts to content height</div> <div class="font-semibold">Responsive: Auto height</div>
<div class="text-sm text-gray-500">Adjusts to content height</div>
</div> </div>
<div class="responsive-option" onclick="selectResponsive(this, 'iframe', 'full')"> <div class="responsive-option bg-white border-2 border-gray-200 p-4 rounded-xl cursor-pointer hover:border-temple-red transition"
<strong>Full Screen:</strong> 100vh onclick="selectResponsive(this, 'iframe', 'full')">
<div class="text-muted small">Takes full viewport height</div> <div class="font-semibold">Full Screen: 100vh</div>
<div class="text-sm text-gray-500">Takes full viewport height</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Popup Tab --> <!-- Popup Tab -->
<div class="tab-pane fade" id="popup" role="tabpanel"> <div class="tab-pane hidden" id="popup-pane">
<h5 class="mb-3">Popup Embed Code</h5> <h5 class="text-lg font-semibold text-gray-900 mb-3">Popup Embed Code</h5>
<p class="text-muted">Add a button or link that opens the form in a modal popup.</p> <p class="text-gray-600 mb-4">Add a button or link that opens to form in a modal popup.</p>
<div class="code-block"> <div class="code-block">
<button class="copy-btn" onclick="copyToClipboard(this, 'popup-code')"> <button class="copy-btn" onclick="copyToClipboard(this, 'popup-code')">
<i class="fas fa-copy"></i> Copy <i data-lucide="copy" class="w-3 h-3"></i> Copy
</button> </button>
<code id="popup-code"><button onclick="openFormPopup()" class="btn btn-primary"> <code id="popup-code"><button onclick="openFormPopup()" class="bg-temple-red hover:bg-[#7a1a29] text-white px-6 py-3 rounded-xl font-medium transition">
Open Form Open Form
</button> </button>
@ -319,51 +204,59 @@ function openFormPopup() {
</script></code> </script></code>
</div> </div>
<div class="mt-3"> <div class="mt-6">
<h6>Customization Options</h6> <h6 class="font-semibold text-gray-900 mb-3">Customization Options</h6>
<ul class="feature-list"> <ul class="space-y-2">
<li><i class="fas fa-check"></i> Custom button text and styling</li> <li class="flex items-center gap-2 text-gray-700">
<li><i class="fas fa-check"></i> Trigger on page load or scroll</li> <i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Custom button text and styling
<li><i class="fas fa-check"></i> Custom modal dimensions</li> </li>
<li><i class="fas fa-check"></i> Close on outside click</li> <li class="flex items-center gap-2 text-gray-700">
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Trigger on page load or scroll
</li>
<li class="flex items-center gap-2 text-gray-700">
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Custom modal dimensions
</li>
<li class="flex items-center gap-2 text-gray-700">
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Close on outside click
</li>
</ul> </ul>
</div> </div>
</div> </div>
<!-- Inline Tab --> <!-- Inline Tab -->
<div class="tab-pane fade" id="inline" role="tabpanel"> <div class="tab-pane hidden" id="inline-pane">
<h5 class="mb-3">Inline Embed Code</h5> <h5 class="text-lg font-semibold text-gray-900 mb-3">Inline Embed Code</h5>
<p class="text-muted">Embed the form HTML directly into your page for maximum customization.</p> <p class="text-gray-600 mb-4">Embed form HTML directly into your page for maximum customization.</p>
<div class="alert alert-info"> <div class="bg-blue-50 border border-blue-200 rounded-xl p-4 mb-4 flex items-start gap-3">
<i class="fas fa-info-circle"></i> <strong>Note:</strong> This option requires more technical knowledge but offers the best integration. <i data-lucide="info" class="w-5 h-5 text-blue-600 shrink-0 mt-0.5"></i>
<div>
<strong class="text-blue-900">Note:</strong> This option requires more technical knowledge but offers best integration.
</div>
</div> </div>
<div class="code-block"> <div class="code-block">
<button class="copy-btn" onclick="copyToClipboard(this, 'inline-code')"> <button class="copy-btn" onclick="copyToClipboard(this, 'inline-code')">
<i class="fas fa-copy"></i> Copy <i data-lucide="copy" class="w-3 h-3"></i> Copy
</button> </button>
<code id="inline-code"><!-- Form CSS --> <code id="inline-code"><!-- Form CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.tailwindcss.com" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<!-- Form Container --> <!-- Form Container -->
<div id="form-{{ form.id }}"> <div id="form-{{ form.id }}">
<div class="text-center py-5"> <div class="text-center py-12">
<div class="spinner-border text-primary" role="status"></div> <div class="inline-block w-8 h-8 border-4 border-temple-red border-t-transparent rounded-full animate-spin"></div>
<p class="mt-3">Loading form...</p> <p class="mt-4 text-gray-600">Loading form...</p>
</div> </div>
</div> </div>
<!-- Form Scripts --> <!-- Form Scripts -->
<script src="https://unpkg.com/preact@10.19.3/dist/preact.umd.js"></script>
<script src="https://unpkg.com/htm@3.1.1/dist/htm.umd.js"></script>
<script> <script>
// Load form data and render // Load form data and render
fetch('/recruitment/api/forms/{{ form.id }}/load/') fetch('/recruitment/api/forms/{{ form.id }}/load/')
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
// Render form using the form structure // Render form using to form structure
console.log('Form data:', data); console.log('Form data:', data);
// Implement form rendering logic here // Implement form rendering logic here
}) })
@ -371,13 +264,21 @@ function openFormPopup() {
</script></code> </script></code>
</div> </div>
<div class="mt-3"> <div class="mt-6">
<h6>Benefits of Inline Embed</h6> <h6 class="font-semibold text-gray-900 mb-3">Benefits of Inline Embed</h6>
<ul class="feature-list"> <ul class="space-y-2">
<li><i class="fas fa-check"></i> Full control over styling</li> <li class="flex items-center gap-2 text-gray-700">
<li><i class="fas fa-check"></i> Better SEO integration</li> <i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Full control over styling
<li><i class="fas fa-check"></i> Faster initial load</li> </li>
<li><i class="fas fa-check"></i> Custom form handling</li> <li class="flex items-center gap-2 text-gray-700">
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Better SEO integration
</li>
<li class="flex items-center gap-2 text-gray-700">
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Faster initial load
</li>
<li class="flex items-center gap-2 text-gray-700">
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Custom form handling
</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -385,39 +286,59 @@ function openFormPopup() {
</div> </div>
<!-- Preview Section --> <!-- Preview Section -->
<div class="card"> <div class="bg-white rounded-2xl shadow-md overflow-hidden mt-6">
<div class="card-header"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<h5 class="mb-0"> <h5 class="font-semibold flex items-center gap-2">
<i class="fas fa-eye"></i> Live Preview <i data-lucide="eye" class="w-5 h-5"></i> Live Preview
</h5> </h5>
</div> </div>
<div class="card-body p-0"> <div class="p-0">
<iframe src="{% url 'form_preview' form.id %}?embed=true" <iframe src="{% url 'form_preview' form.id %}?embed=true"
class="preview-iframe" class="w-full border border-gray-200 rounded-xl"
style="height: 600px;"
frameborder="0"> frameborder="0">
</iframe> </iframe>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script> <script>
lucide.createIcons();
function switchTab(tabName) {
// Update tab buttons
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active', 'text-temple-red', 'border-temple-red');
btn.classList.add('text-gray-600', 'border-transparent');
});
const activeBtn = document.querySelector(`[data-tab="${tabName}"]`);
activeBtn.classList.add('active', 'text-temple-red', 'border-temple-red');
activeBtn.classList.remove('text-gray-600', 'border-transparent');
// Show/hide tab panes
document.querySelectorAll('.tab-pane').forEach(pane => {
pane.classList.add('hidden');
});
document.getElementById(`${tabName}-pane`).classList.remove('hidden');
}
function copyToClipboard(button, elementId) { function copyToClipboard(button, elementId) {
const element = document.getElementById(elementId); const element = document.getElementById(elementId);
const text = element.textContent; const text = element.textContent;
navigator.clipboard.writeText(text).then(function() { navigator.clipboard.writeText(text).then(function() {
const originalText = button.innerHTML; const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-check"></i> Copied!'; button.innerHTML = '<i data-lucide="check" class="w-3 h-3"></i> Copied!';
button.classList.add('copied'); button.classList.add('copied');
lucide.createIcons();
setTimeout(function() { setTimeout(function() {
button.innerHTML = originalText; button.innerHTML = originalText;
button.classList.remove('copied'); button.classList.remove('copied');
lucide.createIcons();
}, 2000); }, 2000);
}).catch(function(err) { }).catch(function(err) {
console.error('Failed to copy text: ', err); console.error('Failed to copy text: ', err);
@ -431,12 +352,14 @@ function openFormPopup() {
try { try {
document.execCommand('copy'); document.execCommand('copy');
button.innerHTML = '<i class="fas fa-check"></i> Copied!'; button.innerHTML = '<i data-lucide="check" class="w-3 h-3"></i> Copied!';
button.classList.add('copied'); button.classList.add('copied');
lucide.createIcons();
setTimeout(function() { setTimeout(function() {
button.innerHTML = '<i class="fas fa-copy"></i> Copy'; button.innerHTML = originalText;
button.classList.remove('copied'); button.classList.remove('copied');
lucide.createIcons();
}, 2000); }, 2000);
} catch (err) { } catch (err) {
console.error('Fallback copy failed: ', err); console.error('Fallback copy failed: ', err);
@ -450,13 +373,15 @@ function openFormPopup() {
// Remove active class from all options in this tab // Remove active class from all options in this tab
const container = element.closest('.tab-pane'); const container = element.closest('.tab-pane');
container.querySelectorAll('.responsive-option').forEach(opt => { container.querySelectorAll('.responsive-option').forEach(opt => {
opt.classList.remove('active'); opt.classList.remove('active', 'border-temple-red');
opt.classList.add('border-gray-200');
}); });
// Add active class to selected option // Add active class to selected option
element.classList.add('active'); element.classList.add('active', 'border-temple-red');
element.classList.remove('border-gray-200');
// Update the embed code based on selection // Update embed code based on selection
updateEmbedCode(type, option); updateEmbedCode(type, option);
} }
@ -489,14 +414,6 @@ function openFormPopup() {
document.getElementById('iframe-code').textContent = code; document.getElementById('iframe-code').textContent = code;
} }
} }
// Initialize tooltips
document.addEventListener('DOMContentLoaded', function() {
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
});
</script> </script>
</body> </body>
</html> </html>

View File

@ -3,30 +3,35 @@
{% block title %}Forms - University ATS{% endblock %} {% block title %}Forms - University ATS{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="row"> <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
<div class="col-12"> <div class="flex-1">
<div class="d-flex justify-content-between align-items-center mb-4"> <h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
<h1><i class="fas fa-wpforms"></i> Forms</h1> <div class="bg-temple-red/10 p-3 rounded-xl">
<a href="{% url 'form_builder' %}" class="btn btn-primary"> <i data-lucide="layout-template" class="w-8 h-8 text-temple-red"></i>
<i class="fas fa-plus"></i> Create New Form </div>
Forms
</h1>
</div>
<a href="{% url 'form_builder' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
<i data-lucide="plus" class="w-5 h-5"></i> Create New Form
</a> </a>
</div> </div>
<!-- Search and Filter --> <!-- Search and Filter -->
<div class="card mb-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-6">
<div class="card-body"> <div class="p-6">
<form method="get" class="row g-3"> <form method="get" class="flex flex-col md:flex-row gap-4">
<div class="col-md-8"> <div class="flex-1">
<div class="input-group"> <div class="relative">
<input type="text" class="form-control" name="search" placeholder="Search forms..." value="{{ request.GET.search }}"> <input type="text" class="w-full px-4 py-2.5 pr-12 border border-gray-300 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" name="search" placeholder="Search forms..." value="{{ request.GET.search }}">
<button class="btn btn-outline-secondary" type="submit"> <button class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-temple-red transition" type="submit">
<i class="fas fa-search"></i> <i data-lucide="search" class="w-5 h-5"></i>
</button> </button>
</div> </div>
</div> </div>
<div class="col-md-4"> <div class="md:w-64">
<select class="form-select" name="sort"> <select class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 bg-white focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" name="sort">
<option value="-created_at">Latest First</option> <option value="-created_at">Latest First</option>
<option value="created_at">Oldest First</option> <option value="created_at">Oldest First</option>
<option value="title">Title (A-Z)</option> <option value="title">Title (A-Z)</option>
@ -39,104 +44,102 @@
<!-- Forms List --> <!-- Forms List -->
{% if page_obj %} {% if page_obj %}
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for form in page_obj %} {% for form in page_obj %}
<div class="col-lg-4 col-md-6 mb-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
<div class="card h-100"> <div class="p-6">
<div class="card-body"> <div class="flex justify-between items-start mb-3">
<div class="d-flex justify-content-between align-items-start mb-2"> <h5 class="text-lg font-bold text-gray-900 mb-1">{{ form.title }}</h5>
<h5 class="card-title mb-1">{{ form.title }}</h5> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-bold uppercase bg-emerald-100 text-emerald-800">Active</span>
<span class="badge bg-success">Active</span>
</div> </div>
<p class="card-text text-muted small"> <p class="text-sm text-gray-600 mb-4 line-clamp-2">
{{ form.description|truncatewords:15 }} {{ form.description|truncatewords:15 }}
</p> </p>
<div class="mb-2"> <div class="space-y-2 mb-4">
<small class="text-muted"> <div class="flex items-center gap-2 text-sm text-gray-600">
<i class="fas fa-user"></i> {{ form.created_by.username }} <i data-lucide="user" class="w-4 h-4 text-gray-400"></i>
</small> {{ form.created_by.username }}
</div> </div>
<div class="flex items-center gap-2 text-sm text-gray-600">
<div class="mb-2"> <i data-lucide="calendar" class="w-4 h-4 text-gray-400"></i>
<small class="text-muted"> {{ form.created_at|date:"M d, Y" }}
<i class="fas fa-calendar"></i> {{ form.created_at|date:"M d, Y" }}
</small>
</div> </div>
<div class="flex items-center gap-2 text-sm text-gray-600">
<div class="mb-3"> <i data-lucide="bar-chart-3" class="w-4 h-4 text-gray-400"></i>
<small class="text-muted"> {{ form.submissions.count }} submissions
<i class="fas fa-chart-bar"></i> {{ form.submissions.count }} submissions
</small>
</div> </div>
</div> </div>
<div class="card-footer bg-transparent"> </div>
<div class="btn-group w-100" role="group"> <div class="border-t border-gray-100 p-4 bg-gray-50/50">
<div class="flex flex-wrap gap-2">
{% if form.created_by == user %} {% if form.created_by == user %}
<a href="{% url 'edit_form' form.slug %}" class="btn btn-sm btn-outline-warning"> <a href="{% url 'edit_form' form.slug %}" class="inline-flex items-center gap-1 border border-amber-300 text-amber-700 hover:bg-amber-50 px-3 py-2 rounded-lg text-xs font-semibold transition">
<i class="fas fa-edit"></i> Edit <i data-lucide="edit-3" class="w-3 h-3"></i> Edit
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'form_preview' form.slug %}" class="btn btn-sm btn-outline-primary" target="_blank"> <a href="{% url 'form_preview' form.slug %}" class="inline-flex items-center gap-1 border border-temple-red text-temple-red hover:bg-temple-red/10 px-3 py-2 rounded-lg text-xs font-semibold transition" target="_blank">
<i class="fas fa-eye"></i> Preview <i data-lucide="eye" class="w-3 h-3"></i> Preview
</a> </a>
<a href="{% url 'form_embed' form.slug %}" class="btn btn-sm btn-outline-secondary" target="_blank"> <a href="{% url 'form_embed' form.slug %}" class="inline-flex items-center gap-1 border border-gray-300 text-gray-600 hover:bg-gray-100 px-3 py-2 rounded-lg text-xs font-semibold transition" target="_blank">
<i class="fas fa-code"></i> Embed <i data-lucide="code-2" class="w-3 h-3"></i> Embed
</a> </a>
<a href="{% url 'form_submissions' form.slug %}" class="btn btn-sm btn-outline-info"> <a href="{% url 'form_submissions' form.slug %}" class="inline-flex items-center gap-1 border border-blue-300 text-blue-700 hover:bg-blue-50 px-3 py-2 rounded-lg text-xs font-semibold transition">
<i class="fas fa-list"></i> Submissions <i data-lucide="list" class="w-3 h-3"></i> Submissions
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<nav aria-label="Forms pagination"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 mt-6">
<ul class="pagination justify-content-center"> <div class="flex flex-col md:flex-row justify-between items-center gap-4">
<div class="text-sm text-gray-600">
Showing page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</div>
<nav aria-label="Forms pagination" class="flex items-center gap-2">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item"> <a href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="First">
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">First</a> <i data-lucide="chevrons-left" class="w-4 h-4"></i>
</li> </a>
<li class="page-item"> <a href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Previous">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Previous</a> <i data-lucide="chevron-left" class="w-4 h-4"></i>
</li> </a>
{% endif %} {% endif %}
<li class="page-item active"> <span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span> {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</li> </span>
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="page-item"> <a href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Next">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Next</a> <i data-lucide="chevron-right" class="w-4 h-4"></i>
</li> </a>
<li class="page-item"> <a href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Last">
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Last</a> <i data-lucide="chevrons-right" class="w-4 h-4"></i>
</li> </a>
{% endif %} {% endif %}
</ul>
</nav> </nav>
</div>
</div>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="text-center py-5"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
<i class="fas fa-wpforms fa-3x text-muted mb-3"></i> <i data-lucide="layout-template" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
<h3>No forms found</h3> <h3 class="text-xl font-semibold text-gray-900 mb-2">No forms found</h3>
<p class="text-muted">Create your first form to get started.</p> <p class="text-gray-500 mb-6">Create your first form to get started.</p>
<a href="{% url 'form_builder' %}" class="btn btn-primary">Create Form</a> <a href="{% url 'form_builder' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
<i data-lucide="plus" class="w-5 h-5"></i> Create Form
</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script> <script>
// Add any interactive JavaScript here if needed lucide.createIcons();
</script> </script>
{% endblock %} {% endblock %}

View File

@ -4,59 +4,60 @@
{% block content %} {% block content %}
{% if not is_embed %} {% if not is_embed %}
<div class="container-fluid"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="row"> <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
<div class="col-12"> <div class="flex-1">
<div class="d-flex justify-content-between align-items-center mb-4"> <h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
<h1><i class="fas fa-wpforms"></i> Form Preview</h1> <div class="bg-temple-red/10 p-3 rounded-xl">
<div> <i data-lucide="layout-template" class="w-8 h-8 text-temple-red"></i>
<a href="{% url 'form_list' %}" class="btn btn-outline-secondary me-2">
<i class="fas fa-arrow-left"></i> Back to Forms
</a>
<a href="{% url 'form_embed' form.id %}" class="btn btn-outline-primary" target="_blank">
<i class="fas fa-code"></i> Get Embed Code
</a>
</div> </div>
Form Preview
</h1>
</div> </div>
<div class="flex flex-wrap gap-2">
<a href="{% url 'form_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i> Back to Forms
</a>
<a href="{% url 'form_embed' form.id %}" class="inline-flex items-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-4 py-2.5 rounded-xl text-sm transition font-medium" target="_blank">
<i data-lucide="code-2" class="w-4 h-4"></i> Get Embed Code
</a>
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
<!-- Form Preview Container --> <!-- Form Preview Container -->
<div class="{% if is_embed %}embed-container{% else %}container-fluid{% endif %}"> <div class="{% if is_embed %}bg-gray-100 min-h-screen{% else %}max-w-7xl mx-auto px-4 sm:px-6 lg:px-8{% endif %}">
<div class="{% if is_embed %}embed-form-wrapper{% else %}row justify-content-center{% endif %}"> <div class="{% if is_embed %}{% else %}flex justify-center{% endif %}">
<div class="{% if is_embed %}embed-form{% else %}col-lg-8 col-md-10{% endif %}"> <div class="{% if is_embed %}w-full{% else %}w-full max-w-4xl{% endif %}">
<!-- Form Header --> <!-- Form Header -->
<div class="card shadow-sm mb-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
<div class="card-header bg-primary text-white"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-6">
<h3 class="mb-1">{{ form.title }}</h3> <h3 class="text-2xl font-bold mb-2">{{ form.title }}</h3>
{% if form.description %} {% if form.description %}
<p class="mb-0 opacity-90">{{ form.description }}</p> <p class="text-white/90 mb-0">{{ form.description }}</p>
{% endif %} {% endif %}
</div> </div>
<div class="card-body"> <div class="p-6">
<div class="d-flex justify-content-between align-items-center"> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3">
<small class="text-muted"> <span class="text-sm text-gray-600 flex items-center gap-2">
<i class="fas fa-chart-bar"></i> {{ submission_count }} submissions <i data-lucide="bar-chart-3" class="w-4 h-4"></i> {{ submission_count }} submissions
</small> </span>
<small class="text-muted"> <span class="text-sm text-gray-600 flex items-center gap-2">
<i class="fas fa-calendar"></i> Created {{ form.created_at|date:"M d, Y" }} <i data-lucide="calendar" class="w-4 h-4"></i> Created {{ form.created_at|date:"M d, Y" }}
</small> </span>
</div> </div>
</div> </div>
</div> </div>
<!-- Live Form Preview --> <!-- Live Form Preview -->
<div class="card shadow-sm"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="card-body p-0"> <div class="p-0">
<div id="form-preview-container"> <div id="form-preview-container">
<!-- Form will be rendered here by Preact --> <!-- Form will be rendered here by Preact -->
<div class="text-center py-5"> <div class="text-center py-12">
<div class="spinner-border text-primary" role="status"> <div class="inline-block w-12 h-12 border-4 border-temple-red border-t-transparent rounded-full animate-spin"></div>
<span class="visually-hidden">Loading form...</span> <p class="mt-4 text-gray-500">Loading form preview...</p>
</div>
<p class="mt-3 text-muted">Loading form preview...</p>
</div> </div>
</div> </div>
</div> </div>
@ -64,41 +65,35 @@
<!-- Form Analytics (only shown if not embedded) --> <!-- Form Analytics (only shown if not embedded) -->
{% if not is_embed %} {% if not is_embed %}
<div class="card shadow-sm mt-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 mt-6 overflow-hidden">
<div class="card-header"> <div class="border-b border-gray-100 p-6">
<h5><i class="fas fa-chart-line"></i> Form Analytics</h5> <h5 class="text-lg font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="line-chart" class="w-5 h-5 text-temple-red"></i> Form Analytics
</h5>
</div> </div>
<div class="card-body"> <div class="p-6">
<div class="row"> <div class="grid grid-cols-2 md:grid-cols-4 gap-6">
<div class="col-md-3"> <div class="text-center p-4 bg-gray-50 rounded-xl">
<div class="text-center"> <h4 class="text-2xl font-bold text-temple-red">{{ submission_count }}</h4>
<h4 class="text-primary">{{ submission_count }}</h4> <span class="text-sm text-gray-600 mt-1 block">Total Submissions</span>
<small class="text-muted">Total Submissions</small>
</div> </div>
<div class="text-center p-4 bg-gray-50 rounded-xl">
<h4 class="text-2xl font-bold text-emerald-600">{{ form.created_at|timesince }}</h4>
<span class="text-sm text-gray-600 mt-1 block">Time Created</span>
</div> </div>
<div class="col-md-3"> <div class="text-center p-4 bg-gray-50 rounded-xl">
<div class="text-center"> <h4 class="text-2xl font-bold text-blue-600">{{ form.structure.wizards|length|default:0 }}</h4>
<h4 class="text-success">{{ form.created_at|timesince }}</h4> <span class="text-sm text-gray-600 mt-1 block">Form Steps</span>
<small class="text-muted">Time Created</small>
</div> </div>
</div> <div class="text-center p-4 bg-gray-50 rounded-xl">
<div class="col-md-3"> <h4 class="text-2xl font-bold text-amber-600">
<div class="text-center">
<h4 class="text-info">{{ form.structure.wizards|length|default:0 }}</h4>
<small class="text-muted">Form Steps</small>
</div>
</div>
<div class="col-md-3">
<div class="text-center">
<h4 class="text-warning">
{% if form.structure.wizards %} {% if form.structure.wizards %}
{{ form.structure.wizards.0.fields|length|default:0 }} {{ form.structure.wizards.0.fields|length|default:0 }}
{% else %} {% else %}
0 0
{% endif %} {% endif %}
</h4> </h4>
<small class="text-muted">First Step Fields</small> <span class="text-sm text-gray-600 mt-1 block">First Step Fields</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -109,41 +104,47 @@
</div> </div>
<!-- Success Modal --> <!-- Success Modal -->
<div class="modal fade" id="successModal" tabindex="-1" aria-labelledby="successModalLabel" aria-hidden="true"> <div id="successModal" class="fixed inset-0 z-50 hidden">
<div class="modal-dialog modal-dialog-centered"> <div class="absolute inset-0 bg-black/50 transition-opacity" onclick="closeModal('successModal')"></div>
<div class="modal-content"> <div class="relative min-h-screen flex items-center justify-center p-4">
<div class="modal-header bg-success text-white"> <div class="relative bg-white rounded-2xl shadow-xl max-w-md w-full p-6">
<h5 class="modal-title" id="successModalLabel"> <div class="flex items-center justify-between mb-4">
<i class="fas fa-check-circle"></i> Form Submitted Successfully! <h5 class="text-xl font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="check-circle" class="w-6 h-6 text-emerald-500"></i> Form Submitted Successfully!
</h5> </h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" onclick="closeModal('successModal')" class="text-gray-400 hover:text-gray-600 transition">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div> </div>
<div class="modal-body"> <div class="mb-6">
<p class="mb-0">Thank you for submitting the form. Your response has been recorded.</p> <p class="text-gray-600 mb-0">Thank you for submitting this form. Your response has been recorded.</p>
</div> </div>
<div class="modal-footer"> <div class="flex gap-3">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" onclick="closeModal('successModal')" class="flex-1 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">Close</button>
<button type="button" class="btn btn-primary" onclick="resetForm()">Submit Another Response</button> <button type="button" onclick="resetForm(); closeModal('successModal');" class="flex-1 bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2.5 rounded-xl text-sm transition font-medium">Submit Another Response</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Error Modal --> <!-- Error Modal -->
<div class="modal fade" id="errorModal" tabindex="-1" aria-labelledby="errorModalLabel" aria-hidden="true"> <div id="errorModal" class="fixed inset-0 z-50 hidden">
<div class="modal-dialog modal-dialog-centered"> <div class="absolute inset-0 bg-black/50 transition-opacity" onclick="closeModal('errorModal')"></div>
<div class="modal-content"> <div class="relative min-h-screen flex items-center justify-center p-4">
<div class="modal-header bg-danger text-white"> <div class="relative bg-white rounded-2xl shadow-xl max-w-md w-full p-6">
<h5 class="modal-title" id="errorModalLabel"> <div class="flex items-center justify-between mb-4">
<i class="fas fa-exclamation-triangle"></i> Submission Error <h5 class="text-xl font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="alert-triangle" class="w-6 h-6 text-red-500"></i> Submission Error
</h5> </h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" onclick="closeModal('errorModal')" class="text-gray-400 hover:text-gray-600 transition">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div> </div>
<div class="modal-body"> <div class="mb-6">
<p class="mb-0" id="errorMessage">An error occurred while submitting the form. Please try again.</p> <p class="text-gray-600 mb-0" id="errorMessage">An error occurred while submitting the form. Please try again.</p>
</div> </div>
<div class="modal-footer"> <div class="flex gap-3">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" onclick="closeModal('errorModal')" class="flex-1 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">Close</button>
</div> </div>
</div> </div>
</div> </div>
@ -153,36 +154,249 @@
{% block extra_css %} {% block extra_css %}
{% if is_embed %} {% if is_embed %}
<style> <style>
.embed-container {
background: #f8f9fa;
padding: 0;
}
.embed-form-wrapper {
margin: 0;
}
.embed-form {
padding: 0;
}
.embed-form .card {
border: none;
border-radius: 0;
box-shadow: none;
}
.embed-form .card-header {
border-radius: 0;
}
body { body {
background: #f8f9fa; background: #f3f4f6;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
</style> </style>
{% endif %} {% endif %}
<style>
/* Form Styles */
.form-label {
@apply block text-sm font-semibold text-gray-700 mb-2;
}
.form-control {
@apply w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition;
}
.form-control.is-invalid {
@apply border-red-500 focus:border-red-500 focus:ring-red-500/20;
}
.form-select {
@apply w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 bg-white focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition;
}
.form-select.is-invalid {
@apply border-red-500 focus:border-red-500 focus:ring-red-500/20;
}
.form-check {
@apply flex items-start gap-3 mb-3;
}
.form-check-input {
@apply mt-1 w-4 h-4 text-temple-red border-gray-300 rounded focus:ring-2 focus:ring-temple-red/20;
}
.form-check-label {
@apply text-sm text-gray-700 cursor-pointer;
}
.invalid-feedback {
@apply text-red-500 text-sm mt-1;
}
.text-danger {
@apply text-red-500;
}
.text-muted {
@apply text-gray-500;
}
.form-text {
@apply text-sm text-gray-500;
}
/* Progress Bar */
.progress {
@apply w-full bg-gray-200 rounded-full overflow-hidden;
}
.progress-bar {
@apply bg-temple-red h-full transition-all duration-300;
}
/* Rating Stars */
.rating-star {
@apply cursor-pointer transition-transform hover:scale-110;
}
.rating-star.active {
@apply text-amber-400;
}
/* FilePond Custom Styles */
.filepond--root {
margin-bottom: 0;
}
.filepond--drop-label {
@apply text-gray-600;
}
.filepond--label-action {
@apply text-temple-red font-semibold hover:text-temple-dark;
}
/* Button Styles */
.btn {
@apply inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition cursor-pointer border-0 outline-none;
}
.btn-primary {
@apply bg-temple-red hover:bg-[#7a1a29] text-white;
}
.btn-primary:disabled {
@apply opacity-50 cursor-not-allowed;
}
.btn-outline-secondary {
@apply border border-gray-300 text-gray-600 hover:bg-gray-100;
}
.btn-outline-secondary:disabled {
@apply opacity-50 cursor-not-allowed;
}
.btn-secondary {
@apply border border-gray-300 text-gray-600 hover:bg-gray-100;
}
/* Spinner */
.spinner-border {
@apply inline-block w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin;
}
.spinner-border-sm {
@apply w-3 h-3 border-2;
}
.spinner-border.text-primary {
@apply border-temple-red text-temple-red;
}
/* Utility */
.visually-hidden {
@apply absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0;
}
.d-flex {
@apply flex;
}
.justify-content-between {
@apply justify-between;
}
.align-items-center {
@apply items-center;
}
.mb-3 {
@apply mb-3;
}
.mb-4 {
@apply mb-4;
}
.me-2 {
@apply me-2;
}
.mt-1 {
@apply mt-1;
}
.mt-4 {
@apply mt-4;
}
.py-5 {
@apply py-12;
}
.text-center {
@apply text-center;
}
.text-primary {
@apply text-temple-red;
}
.text-success {
@apply text-emerald-600;
}
.text-info {
@apply text-blue-600;
}
.text-warning {
@apply text-amber-600;
}
.fa-4x {
@apply text-5xl;
}
.small {
@apply text-sm;
}
.modal {
@apply fixed inset-0 z-50;
}
.modal-dialog-centered {
@apply flex items-center justify-center min-h-screen p-4;
}
.modal-content {
@apply bg-white rounded-2xl shadow-xl max-w-md w-full;
}
.modal-header {
@apply flex items-center justify-between px-6 py-4 border-b border-gray-200;
}
.modal-body {
@apply px-6 py-4;
}
.modal-footer {
@apply flex items-center justify-end gap-3 px-6 py-4 border-t border-gray-200;
}
.modal-title {
@apply text-lg font-bold text-gray-900;
}
.btn-close {
@apply text-gray-400 hover:text-gray-600 transition p-1;
}
.btn-close-white {
@apply text-white hover:text-white/80;
}
.modal-header.bg-success {
@apply bg-emerald-500 text-white;
}
.modal-header.bg-danger {
@apply bg-red-500 text-white;
}
.modal-header.bg-primary {
@apply bg-temple-red text-white;
}
</style>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
@ -440,7 +654,7 @@ class PreviewRatingField extends Component {
<div class="rating-container"> <div class="rating-container">
${[1, 2, 3, 4, 5].map(n => html` ${[1, 2, 3, 4, 5].map(n => html`
<span class="rating-star ${value >= n ? 'active' : ''}" <span class="rating-star ${value >= n ? 'active' : ''}"
style="font-size: 24px; color: ${value >= n ? '#ffc107' : '#ddd'}; cursor: pointer; margin-right: 5px;" style="font-size: 24px; color: ${value >= n ? '#fbbf24' : '#ddd'}; cursor: pointer; margin-right: 5px;"
onClick=${() => onChange(field.id, n)}> onClick=${() => onChange(field.id, n)}>
</span> </span>
@ -590,16 +804,14 @@ class FormPreview extends Component {
if (result.success) { if (result.success) {
this.setState({ isSubmitted: true }); this.setState({ isSubmitted: true });
const modal = new bootstrap.Modal(document.getElementById('successModal')); openModal('successModal');
modal.show();
} else { } else {
throw new Error(result.error || 'Submission failed'); throw new Error(result.error || 'Submission failed');
} }
} catch (error) { } catch (error) {
console.error('Submission error:', error); console.error('Submission error:', error);
document.getElementById('errorMessage').textContent = error.message; document.getElementById('errorMessage').textContent = error.message;
const modal = new bootstrap.Modal(document.getElementById('errorModal')); openModal('errorModal');
modal.show();
} finally { } finally {
this.setState({ isSubmitting: false }); this.setState({ isSubmitting: false });
} }
@ -621,10 +833,12 @@ class FormPreview extends Component {
if (this.state.isSubmitted) { if (this.state.isSubmitted) {
return html` return html`
<div class="text-center py-5"> <div class="text-center py-12">
<i class="fas fa-check-circle text-success fa-4x mb-3"></i> <div class="inline-flex items-center justify-center w-16 h-16 bg-emerald-100 rounded-full mb-4">
<h3>Thank You!</h3> <i data-lucide="check-circle" class="w-8 h-8 text-emerald-600"></i>
<p class="text-muted">Your form has been submitted successfully.</p> </div>
<h3 class="text-2xl font-bold text-gray-900 mb-2">Thank You!</h3>
<p class="text-gray-500 mb-6">Your form has been submitted successfully.</p>
<button class="btn btn-primary" onClick=${this.resetForm}> <button class="btn btn-primary" onClick=${this.resetForm}>
Submit Another Response Submit Another Response
</button> </button>
@ -633,10 +847,10 @@ class FormPreview extends Component {
} }
return html` return html`
<div class="form-preview"> <div class="form-preview p-6">
<!-- Progress Bar --> <!-- Progress Bar -->
${formStructure.settings && formStructure.settings.showProgress && this.getTotalWizards() > 1 ? html` ${formStructure.settings && formStructure.settings.showProgress && this.getTotalWizards() > 1 ? html`
<div class="progress mb-4" style="height: 6px;"> <div class="progress mb-6" style="height: 6px;">
<div class="progress-bar" <div class="progress-bar"
style="width: ${this.getProgress()}%; transition: width 0.3s ease;"> style="width: ${this.getProgress()}%; transition: width 0.3s ease;">
</div> </div>
@ -645,7 +859,7 @@ class FormPreview extends Component {
<!-- Wizard Title --> <!-- Wizard Title -->
${currentWizard ? html` ${currentWizard ? html`
<h4 class="mb-4">${currentWizard.title || 'Step ' + (this.state.currentWizardIndex + 1)}</h4> <h4 class="text-xl font-bold text-gray-900 mb-6">${currentWizard.title || 'Step ' + (this.state.currentWizardIndex + 1)}</h4>
` : ''} ` : ''}
<!-- Form Fields --> <!-- Form Fields -->
@ -661,25 +875,25 @@ class FormPreview extends Component {
</form> </form>
<!-- Navigation Buttons --> <!-- Navigation Buttons -->
<div class="d-flex justify-content-between mt-4"> <div class="flex flex-col sm:flex-row justify-between items-center gap-3 mt-6">
<button type="button" <button type="button"
class="btn btn-outline-secondary" class="btn btn-outline-secondary w-full sm:w-auto"
onClick=${this.handlePrevious} onClick=${this.handlePrevious}
disabled=${this.state.currentWizardIndex === 0 || this.state.isSubmitting}> disabled=${this.state.currentWizardIndex === 0 || this.state.isSubmitting}>
<i class="fas fa-arrow-left"></i> Previous <i data-lucide="arrow-left" class="w-4 h-4"></i> Previous
</button> </button>
<button type="button" <button type="button"
class="btn btn-primary" class="btn btn-primary w-full sm:w-auto"
onClick=${this.handleNext} onClick=${this.handleNext}
disabled=${this.state.isSubmitting}> disabled=${this.state.isSubmitting}>
${this.state.isSubmitting ? html` ${this.state.isSubmitting ? html`
<span class="spinner-border spinner-border-sm me-2" role="status"></span> <span class="spinner-border spinner-border-sm mr-2"></span>
Submitting... Submitting...
` : ''} ` : ''}
${this.state.currentWizardIndex === this.getTotalWizards() - 1 ? ${this.state.currentWizardIndex === this.getTotalWizards() - 1 ?
html`<i class="fas fa-check"></i> Submit` : html`<i data-lucide="check" class="w-4 h-4"></i> Submit` :
html`Next <i class="fas fa-arrow-right"></i>` html`Next <i data-lucide="arrow-right" class="w-4 h-4"></i>`
} }
</button> </button>
</div> </div>
@ -688,6 +902,39 @@ class FormPreview extends Component {
} }
} }
// Modal functions
function openModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.classList.remove('hidden');
setTimeout(() => {
const backdrop = modal.querySelector('.absolute');
if (backdrop) backdrop.classList.remove('opacity-0');
const content = modal.querySelector('.relative.bg-white');
if (content) {
content.classList.remove('opacity-0', 'scale-95');
content.classList.add('opacity-100', 'scale-100');
}
}, 10);
}
}
function closeModal(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
const backdrop = modal.querySelector('.absolute');
if (backdrop) backdrop.classList.add('opacity-0');
const content = modal.querySelector('.relative.bg-white');
if (content) {
content.classList.remove('opacity-100', 'scale-100');
content.classList.add('opacity-0', 'scale-95');
}
setTimeout(() => {
modal.classList.add('hidden');
}, 300);
}
}
// Initialize FilePond plugins // Initialize FilePond plugins
if (typeof FilePond !== 'undefined') { if (typeof FilePond !== 'undefined') {
FilePond.registerPlugin( FilePond.registerPlugin(
@ -696,11 +943,12 @@ if (typeof FilePond !== 'undefined') {
); );
} }
// Render the form preview // Render form preview
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const container = document.getElementById('form-preview-container'); const container = document.getElementById('form-preview-container');
if (container) { if (container) {
render(html`<${FormPreview} />`, container); render(html`<${FormPreview} />`, container);
lucide.createIcons();
} }
}); });
@ -709,7 +957,11 @@ window.resetForm = function() {
const container = document.getElementById('form-preview-container'); const container = document.getElementById('form-preview-container');
if (container) { if (container) {
render(html`<${FormPreview} />`, container); render(html`<${FormPreview} />`, container);
lucide.createIcons();
} }
}; };
window.closeModal = closeModal;
window.openModal = openModal;
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,201 +1,67 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n static %} {% load i18n static form_filters %}
{% load form_filters %}
{% block title %}{{ form.name }} - {% trans "Submission Details" %}{% endblock %} {% block title %}{{ form.name }} - {% trans "Submission Details" %}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES */
/* ================================================= */
:root {
--kaauh-teal: #00636e; /* Primary */
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
.text-info { color: #17a2b8 !important; }
.text-success { color: #28a745 !important; }
.text-secondary { color: #6c757d !important; }
.bg-info { background-color: #17a2b8 !important; }
.bg-secondary { background-color: #6c757d !important; }
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
.card-header {
font-weight: 600;
padding: 1rem 1.25rem;
background-color: #f8f9fa; /* Light background */
border-bottom: 1px solid var(--kaauh-border);
}
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.5rem 1rem;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary outline button */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
font-weight: 500;
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* ================================================= */
/* RESPONSES TABLE SPECIFIC STYLES (Horizontal Layout) */
/* ================================================= */
/* Main table container */
.table-submission {
margin-bottom: 0;
border: none;
table-layout: auto; /* Allow columns to resize based on content */
}
/* Fixed first column (the row headers) */
.table-submission th:first-child,
.table-submission td:first-child {
background-color: #f0f4f7; /* Slightly darker than header */
font-weight: 600;
color: var(--kaauh-primary-text);
width: 150px;
min-width: 150px;
position: sticky; /* Keep it visible when scrolling right */
left: 0;
z-index: 2;
border-right: 1px solid var(--kaauh-border);
}
.table-submission th:first-child {
top: 0; /* Important for sticky header/row-header intersection */
}
/* Field Label Header Row (Top Row) */
.table-submission thead th {
font-weight: 600;
background-color: #e9ecef; /* Light gray for headers */
color: var(--kaauh-primary-text);
vertical-align: middle;
text-align: center;
border: 1px solid var(--kaauh-border);
border-bottom: 2px solid var(--kaauh-teal); /* Highlight the bottom of the header */
padding: 0.75rem 0.5rem;
}
.table-submission thead th:not(:first-child) {
min-width: 200px; /* Give response columns space */
max-width: 300px;
white-space: normal;
word-wrap: break-word;
}
/* Data Cells */
.table-submission tbody td {
vertical-align: top;
padding: 0.75rem 0.75rem;
border: 1px solid var(--kaauh-border);
border-top: none;
}
/* Styling for multi-value responses */
.table-submission .badge {
font-weight: 500;
margin-top: 0.25rem;
}
/* File display */
.table-submission .fa-file {
color: var(--kaauh-teal);
}
.table-submission .btn-outline-primary {
color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<nav aria-label="breadcrumb"> <!-- Breadcrumb -->
<ol class="breadcrumb"> <nav class="mb-6" aria-label="breadcrumb">
<li class="breadcrumb-item "><a href="{% url 'job_detail' submission.template.job.slug %}" class="text-secondary text-decoration-none">{% trans "Job Detail" %}</a></li> <ol class="flex items-center gap-2 text-sm flex-wrap">
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}" class="text-secondary text-decoration-none">{% trans "Form Template" %}</a></li> <li><a href="{% url 'job_detail' submission.template.job.slug %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Job Detail" %}</a></li>
<li class="breadcrumb-item active" aria-current="page" style=" <li class="text-gray-400">/</li>
color: #F43B5E; /* Rosy Accent Color */ <li><a href="{% url 'form_builder' submission.template.pk%}" class="text-gray-500 hover:text-temple-red transition">{% trans "Form Template" %}</a></li>
font-weight: 600; <li class="text-temple-red font-semibold">{% trans "Submission Details" %}</li>
">{% trans "Submission Details" %}</li>
</ol> </ol>
</nav> </nav>
<div class="row">
<div class="col-12"> <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="flex-1">
<h2 class="text-primary fw-bold">{% trans "Submission Details" %}</h2> <h2 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary"> <div class="bg-temple-red/10 p-3 rounded-xl">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %} <i data-lucide="file-check-2" class="w-8 h-8 text-temple-red"></i>
</div>
{% trans "Submission Details" %}
</h2>
</div>
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Submissions" %}
</a> </a>
</div> </div>
</div>
</div>
<div class="card shadow-sm mb-4"> <!-- Submission Metadata -->
<div class="card-header"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-6 overflow-hidden">
<h5 class="mb-0">{% trans "Submission Metadata" %}</h5> <div class="border-b border-gray-100 px-6 py-4">
<h5 class="text-lg font-bold text-gray-900">{% trans "Submission Metadata" %}</h5>
</div> </div>
<div class="card-body small"> <div class="p-6">
<div class="row g-3"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="col-md-4"> <div class="flex items-center gap-2">
<i class="fas fa-fingerprint me-2 text-primary"></i> <i data-lucide="fingerprint" class="w-5 h-5 text-temple-red"></i>
<strong>{% trans "Submission ID:" %}</strong> <span class="text-secondary">{{ submission.id }}</span> <span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Submission ID:" %}</strong> {{ submission.id }}</span>
</div> </div>
<div class="col-md-4"> <div class="flex items-center gap-2">
<i class="fas fa-calendar-check me-2 text-primary"></i> <i data-lucide="calendar-check" class="w-5 h-5 text-temple-red"></i>
<strong>{% trans "Submitted:" %}</strong> <span class="text-secondary">{{ submission.submitted_at|date:"M d, Y H:i" }}</span> <span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Submitted:" %}</strong> {{ submission.submitted_at|date:"M d, Y H:i" }}</span>
</div> </div>
<div class="col-md-4"> <div class="flex items-center gap-2">
<i class="fas fa-file-alt me-2 text-primary"></i> <i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
<strong>{% trans "Form:" %}</strong> <span class="text-secondary">{{ submission.template.name }}</span> <span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Form:" %}</strong> {{ submission.template.name }}</span>
</div> </div>
</div> </div>
{% if submission.applicant_name or submission.applicant_email %} {% if submission.applicant_name or submission.applicant_email %}
<div class="row g-3 mt-1"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
{% if submission.applicant_name %} {% if submission.applicant_name %}
<div class="col-md-4"> <div class="flex items-center gap-2">
<i class="fas fa-user me-2 text-primary"></i> <i data-lucide="user" class="w-5 h-5 text-temple-red"></i>
<strong>{% trans "Applicant Name:" %}</strong> <span class="text-secondary">{{ submission.applicant_name }}</span> <span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Applicant Name:" %}</strong> {{ submission.applicant_name }}</span>
</div> </div>
{% endif %} {% endif %}
{% if submission.applicant_email %} {% if submission.applicant_email %}
<div class="col-md-4"> <div class="flex items-center gap-2">
<i class="fas fa-envelope me-2 text-primary"></i> <i data-lucide="mail" class="w-5 h-5 text-temple-red"></i>
<strong>{% trans "Email:" %}</strong> <span class="text-secondary">{{ submission.applicant_email }}</span> <span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Email:" %}</strong> {{ submission.applicant_email }}</span>
</div> </div>
{% endif %} {% endif %}
</div> </div>
@ -203,71 +69,79 @@
</div> </div>
</div> </div>
<div class="card shadow-sm"> <!-- Form Responses -->
<div class="card-header"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<h5 class="mb-0">{% trans "Form Responses" %}</h5> <div class="border-b border-gray-100 px-6 py-4">
<h5 class="text-lg font-bold text-gray-900">{% trans "Form Responses" %}</h5>
</div> </div>
<div class="card-body p-0"> <div class="p-0">
{% with submission=submission %} {% with submission=submission %}
{% get_all_responses_flat submission as flat_responses %} {% get_all_responses_flat submission as flat_responses %}
{% if flat_responses %} {% if flat_responses %}
<div class="table-responsive"> <div class="overflow-x-auto">
<table class="table table-submission table-hover"> <table class="min-w-full divide-y divide-gray-200">
<thead> <thead class="bg-gray-50">
<tr> <tr>
<th scope="col">{% trans "Field Property" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider uppercase whitespace-nowrap sticky left-0 bg-gray-50 z-20 border-r border-gray-200 min-w-[150px]">{% trans "Field Property" %}</th>
{% for response in flat_responses %} {% for response in flat_responses %}
<th scope="col">{{ response.field_label }}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider uppercase whitespace-nowrap min-w-[200px] max-w-[300px]">{{ response.field_label }}</th>
{% endfor %} {% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-200">
<!-- Response Value Row -->
<tr> <tr>
<td><strong>{% trans "Response Value" %}</strong></td> <td class="px-6 py-4 font-semibold text-gray-900 sticky left-0 bg-white z-10 border-r border-gray-200 whitespace-nowrap">{% trans "Response Value" %}</td>
{% for response in flat_responses %} {% for response in flat_responses %}
<td> <td class="px-6 py-4 text-sm text-gray-700 max-w-[300px]">
{% if response.uploaded_file %} {% if response.uploaded_file %}
<div> <div class="space-y-2">
<span class="d-block text-truncate" style="max-width: 180px;"><i class="fas fa-file me-1"></i> {{ response.uploaded_file.name }}</span> <span class="block text-sm text-gray-600 truncate max-w-[180px] inline-flex items-center gap-1">
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-secondary mt-1" target="_blank" title="{% trans 'Download File' %}"> <i data-lucide="file" class="w-4 h-4"></i> {{ response.uploaded_file.name }}
<i class="fas fa-download"></i> {% trans "Download" %} </span>
<a href="{{ response.uploaded_file.url }}" class="inline-flex items-center gap-1 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-3 py-1.5 rounded-lg text-xs font-semibold transition" target="_blank" title="{% trans 'Download File' %}">
<i data-lucide="download" class="w-3 h-3"></i> {% trans "Download" %}
</a> </a>
</div> </div>
{% elif response.value %} {% elif response.value %}
{% if response.field_type == 'checkbox' and response.value|length > 0 %} {% if response.field_type == 'checkbox' and response.value|length > 0 %}
<div class="d-flex flex-wrap gap-1"> <div class="flex flex-wrap gap-1">
{% for val in response.value %} {% for val in response.value %}
<span class="badge bg-secondary">{{ val }}</span> <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold uppercase bg-gray-200 text-gray-700">{{ val }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% elif response.field_type == 'radio' or response.field_type == 'select' %} {% elif response.field_type == 'radio' or response.field_type == 'select' %}
<span class="badge bg-info">{{ response.value }}</span> <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold uppercase bg-blue-100 text-blue-800">{{ response.value }}</span>
{% else %} {% else %}
<p class="mb-0 small text-wrap">{{ response.value|linebreaksbr }}</p> <p class="text-sm text-gray-700 line-clamp-3">{{ response.value|linebreaksbr }}</p>
{% endif %} {% endif %}
{% else %} {% else %}
<span class="text-muted small">{% trans "Not provided" %}</span> <span class="text-sm text-gray-400">{% trans "Not provided" %}</span>
{% endif %} {% endif %}
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
<!-- Associated Stage Row -->
<tr> <tr>
<td><strong>{% trans "Associated Stage" %}</strong></td> <td class="px-6 py-4 font-semibold text-gray-900 sticky left-0 bg-white z-10 border-r border-gray-200 whitespace-nowrap">{% trans "Associated Stage" %}</td>
{% for response in flat_responses %} {% for response in flat_responses %}
<td> <td class="px-6 py-4 text-sm text-gray-600">
<span class="small text-secondary">{{ response.stage_name|default:"N/A" }}</span> {{ response.stage_name|default:"N/A" }}
</td> </td>
{% endfor %} {% endfor %}
</tr> </tr>
<!-- Field Required Row -->
<tr> <tr>
<td><strong>{% trans "Field Required" %}</strong></td> <td class="px-6 py-4 font-semibold text-gray-900 sticky left-0 bg-white z-10 border-r border-gray-200 whitespace-nowrap">{% trans "Field Required" %}</td>
{% for response in flat_responses %} {% for response in flat_responses %}
<td> <td class="px-6 py-4">
{% if response.required %} {% if response.required %}
<span class="text-danger small"><i class="fas fa-asterisk"></i> {% trans "Yes" %}</span> <span class="inline-flex items-center gap-1 text-sm text-red-600 font-semibold">
<i data-lucide="asterisk" class="w-3 h-3"></i> {% trans "Yes" %}
</span>
{% else %} {% else %}
<span class="small text-success">{% trans "No" %}</span> <span class="text-sm text-emerald-600 font-semibold">{% trans "No" %}</span>
{% endif %} {% endif %}
</td> </td>
{% endfor %} {% endfor %}
@ -276,14 +150,18 @@
</table> </table>
</div> </div>
{% else %} {% else %}
<div class="text-center text-muted py-5 px-3"> <div class="text-center py-12 px-6">
<i class="fas fa-exclamation-circle fa-2x mb-3"></i> <i data-lucide="alert-circle" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
<p class="lead">{% trans "No response fields were found for this submission." %}</p> <p class="text-lg font-semibold text-gray-900 mb-2">{% trans "No response fields were found for this submission." %}</p>
<p class="small">{% trans "This may occur if the form template was modified or responses were cleared." %}</p> <p class="text-sm text-gray-500">{% trans "This may occur if the form template was modified or responses were cleared." %}</p>
</div> </div>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
</div> </div>
</div> </div>
</div> </div>
<script>
lucide.createIcons();
</script>
{% endblock %} {% endblock %}

View File

@ -2,306 +2,118 @@
{% load static i18n form_filters %} {% load static i18n form_filters %}
{% block title %}{% trans "All Submissions for" %} {{ template.name }} - ATS{% endblock %} {% block title %}{% trans "All Submissions for" %} {{ template.name }} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* UI Variables (Matching Form Templates List) */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-gray-light: #f8f9fa;
}
/* --- Typography and Color Overrides --- */
.text-primary { color: var(--kaauh-teal) !important; }
/* --- Button Base Styles (Matching Form Templates List) --- */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.375rem 0.75rem;
border-radius: 0.5rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
color: white;
}
/* Secondary Button Style (for Edit/Preview) */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Size Utilities (matching Bootstrap convention) */
.btn-lg {
padding: 0.75rem 1.5rem;
font-size: 1.1rem;
}
.btn-sm {
font-size: 0.8rem;
padding: 0.3rem 0.6rem;
}
/* --- Card and Layout Styles (Matching Form Templates List) --- */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.card-header {
background-color: var(--kaauh-teal-dark) !important;
border-bottom: 1px solid var(--kaauh-border);
color: white !important;
font-weight: 600;
padding: 1rem 1.25rem;
border-radius: 0.75rem 0.75rem 0 0;
}
.card-header h1 {
color: white !important;
font-weight: 700;
font-size: 1.5rem;
}
.card-header .fas {
color: white !important;
}
.card-header .small {
color: rgba(255, 255, 255, 0.7) !important;
}
.card-body {
padding: 1.25rem;
}
/* --- Compact Table Styles --- */
.table-responsive {
border-radius: 0.5rem;
overflow: auto;
max-height: 70vh;
display: flex;
flex-direction: column;
}
.table {
margin-bottom: 0;
min-width: max-content;
}
.table thead {
position: sticky;
top: 0;
z-index: 10;
}
.table thead th {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-border);
font-weight: 600;
text-transform: uppercase;
font-size: 0.7rem;
letter-spacing: 0.3px;
padding: 0.5rem 0.75rem;
white-space: nowrap;
}
.table tbody td {
padding: 0.5rem 0.75rem;
vertical-align: middle;
border-color: var(--kaauh-border);
font-size: 0.9rem;
}
.table tbody tr {
transition: background-color 0.2s;
}
.table tbody tr:hover {
background-color: var(--kaauh-gray-light);
}
/* Compact form elements */
.file-response {
display: flex;
align-items: center;
gap: 0.25rem;
}
.badge-response {
margin: 0.05rem;
font-size: 0.75rem;
padding: 0.2rem 0.4rem;
}
.response-value p {
margin: 0;
font-size: 0.85rem;
}
.btn-sm {
font-size: 0.75rem;
padding: 0.2rem 0.4rem;
}
/* --- Pagination Styling (Matching Form Templates List) --- */
.pagination .page-item .page-link {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-border);
}
.pagination .page-item.active .page-link {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
.pagination .page-item:hover .page-link:not(.active) {
background-color: #e9ecef;
}
.pagination-info {
color: var(--kaauh-primary-text);
font-size: 0.9rem;
}
/* --- Empty State Theming --- */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--kaauh-primary-text);
border: 2px dashed var(--kaauh-border);
border-radius: 0.75rem;
background-color: var(--kaauh-gray-light);
}
.empty-state i {
font-size: 1rem;
margin-bottom: 0.5rem;
color: var(--kaauh-teal-dark);
}
.empty-state .btn-main-action .fas {
color: white !important;
}
/* --- Breadcrumb --- */
.breadcrumb {
background-color: transparent;
padding: 0;
margin-bottom: 1rem;
}
.breadcrumb-item a {
color: var(--kaauh-teal-dark);
text-decoration: none;
}
.breadcrumb-item a:hover {
text-decoration: underline;
}
.breadcrumb-item.active {
color: var(--kaauh-primary-text);
}
/* --- Response Value Styling --- */
.response-value {
word-break: break-word;
max-width: 200px;
}
.file-response {
display: flex;
align-items: center;
gap: 0.5rem;
}
.badge-response {
margin: 0.1rem;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container py-4"> <div class="space-y-6">
<nav aria-label="breadcrumb">
<ol class="breadcrumb"> <!-- Desktop Header -->
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary text-decoration-none">{% trans "Dashboard" %}</a></li> <div class="hidden lg:block">
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary text-decoration-none">{% trans "Form Templates" %}</a></li> <!-- Breadcrumb -->
<li class="breadcrumb-item"><a href="{% url 'form_template_submissions_list' template.slug %}" class="text-secondary text-decoration-none">{% trans "Submissions" %}</a></li> <nav class="mb-6" aria-label="breadcrumb">
<li class="breadcrumb-item active" style=" <ol class="flex items-center gap-2 text-sm flex-wrap">
color: #F43B5E; <li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
font-weight: 600; <i data-lucide="home" class="w-4 h-4"></i> {% trans "Dashboard" %}
">{% trans "All Submissions Table" %}</li> </a></li>
<li class="text-gray-400">/</li>
<li><a href="{% url 'form_templates_list' %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Form Templates" %}</a></li>
<li class="text-gray-400">/</li>
<li><a href="{% url 'form_template_submissions_list' template.slug %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Submissions" %}</a></li>
<li class="text-gray-400">/</li>
<li class="text-temple-red font-semibold">{% trans "All Submissions Table" %}</li>
</ol> </ol>
</nav> </nav>
<div class="card shadow-sm"> <div class="flex justify-between items-start mb-6">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="flex-1">
<div> <h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
<h1 class="h3 mb-1 d-flex align-items-center"> <div class="bg-temple-red/10 p-3 rounded-xl">
<i class="fas fa-table me-2"></i> <i data-lucide="table" class="w-8 h-8 text-temple-red"></i>
{% trans "All Submissions for" %}: <span class="text-white ms-2">{{ template.name }}</span>
</h1>
<small class="text-white-50">Template ID: #{{ template.id }}</small>
</div> </div>
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-light btn-sm"> {% trans "All Submissions for" %}: {{ template.name }}
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %} </h1>
<p class="text-sm text-gray-500 mt-2">Template ID: #{{ template.id }}</p>
</div>
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Submissions" %}
</a> </a>
</div> </div>
<div class="card-body"> </div>
<!-- Mobile Header -->
<div class="lg:hidden mb-4">
<div class="flex items-center justify-between mb-4">
<h1 class="text-xl font-bold text-gray-900 flex items-center gap-2">
<div class="bg-temple-red/10 p-2 rounded-lg">
<i data-lucide="table" class="w-5 h-5 text-temple-red"></i>
</div>
{% trans "All Submissions" %}
</h1>
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back" %}
</a>
</div>
</div>
{% if page_obj.object_list %} {% if page_obj.object_list %}
<div class="table-responsive"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<table class="table table-hover"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<thead> <h5 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="list" class="w-5 h-5"></i>
{% trans "All Submissions Table" %}
</h5>
</div>
<div class="overflow-x-auto max-h-[70vh]">
<table class="min-w-full divide-y divide-gray-200">
<thead class="sticky top-0 z-10">
<tr> <tr>
<th scope="col">{% trans "Submission ID" %}</th> <th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Submission ID" %}</th>
<th scope="col">{% trans "Applicant Name" %}</th> <th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Applicant Name" %}</th>
<th scope="col">{% trans "Applicant Email" %}</th> <th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Applicant Email" %}</th>
<th scope="col">{% trans "Submitted At" %}</th> <th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Submitted At" %}</th>
{% for field in fields %} {% for field in fields %}
<th scope="col">{{ field.label }}</th> <th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{{ field.label }}</th>
{% endfor %} {% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-100 text-sm">
{% for submission in page_obj %} {% for submission in page_obj %}
<tr> <tr class="hover:bg-gray-50 transition-colors">
<td class="fw-medium">{{ submission.id }}</td> <td class="px-4 py-2 font-semibold text-gray-900 whitespace-nowrap">{{ submission.id }}</td>
<td>{{ submission.applicant_name|default:"N/A" }}</td> <td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.applicant_name|default:"N/A" }}</td>
<td>{{ submission.applicant_email|default:"N/A" }}</td> <td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.applicant_email|default:"N/A" }}</td>
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td> <td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
{% for field in fields %} {% for field in fields %}
{% get_field_response_for_submission submission field as response %} {% get_field_response_for_submission submission field as response %}
<td class="response-value"> <td class="px-4 py-2 max-w-[200px]">
{% if response %} {% if response %}
{% if response.uploaded_file %} {% if response.uploaded_file %}
<div class="file-response"> <div class="flex items-center gap-2">
<a href="{{ response.uploaded_file.url }}"
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-primary" target="_blank" title="Download File"> class="inline-flex items-center gap-1 bg-temple-red/10 hover:bg-temple-red/20 text-temple-red px-2 py-1 rounded text-xs transition"
<i class="fas fa-download"></i> target="_blank"
title="{% trans 'Download File' %}">
<i data-lucide="download" class="w-3 h-3"></i> {% trans "Download" %}
</a> </a>
</div> </div>
{% elif response.value %} {% elif response.value %}
{% if response.field.field_type == 'checkbox' and response.value|length > 0 %} {% if response.field.field_type == 'checkbox' and response.value|length > 0 %}
<div> <div class="flex flex-wrap gap-1">
{% for val in response.value|to_list %} {% for val in response.value|to_list %}
<span class="badge bg-secondary badge-response">{{ val }}</span> <span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-gray-200 text-gray-700">{{ val }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% elif response.field.field_type == 'radio' or response.field.field_type == 'select' %} {% elif response.field.field_type == 'radio' or response.field.field_type == 'select' %}
<span class="badge bg-info">{{ response.value }}</span> <span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-blue-100 text-blue-800">{{ response.value }}</span>
{% else %} {% else %}
<p class="mb-0">{{ response.value|linebreaksbr|truncatewords:10 }}</p> <p class="text-xs text-gray-700 line-clamp-2">{{ response.value|linebreaksbr|truncatewords:10 }}</p>
{% endif %} {% endif %}
{% else %} {% else %}
<span class="text-muted">Not provided</span> <span class="text-xs text-gray-400">Not provided</span>
{% endif %} {% endif %}
{% else %} {% else %}
<span class="text-muted">Not provided</span> <span class="text-xs text-gray-400">Not provided</span>
{% endif %} {% endif %}
</td> </td>
{% endfor %} {% endfor %}
@ -310,65 +122,57 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center mt-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
<div class="pagination-info mb-3 mb-md-0"> <div class="flex flex-col md:flex-row justify-between items-center gap-4">
<div class="text-sm text-gray-600">
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %} {% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
Showing {{ start }} to {{ end }} of {{ total }} results. Showing {{ start }} to {{ end }} of {{ total }} results.
{% endblocktrans %} {% endblocktrans %}
</div> </div>
<nav aria-label="Page navigation"> <nav aria-label="Page navigation" class="flex items-center gap-2">
<ul class="pagination pagination-sm mb-0">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item"> <a href="?page=1" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="First">
<a class="page-link" href="?page=1" aria-label="First"> <i data-lucide="chevrons-left" class="w-4 h-4"></i>
<span aria-hidden="true">&laquo;</span>
</a> </a>
</li> <a href="?page={{ page_obj.previous_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Previous">
<li class="page-item"> <i data-lucide="chevron-left" class="w-4 h-4"></i>
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">&lsaquo;</span>
</a> </a>
</li>
{% endif %} {% endif %}
<li class="page-item active"> <span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
<span class="page-link">
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }} {% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
</span> </span>
</li>
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="page-item"> <a href="?page={{ page_obj.next_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Next">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next"> <i data-lucide="chevron-right" class="w-4 h-4"></i>
<span aria-hidden="true">&rsaquo;</span>
</a> </a>
</li> <a href="?page={{ page_obj.paginator.num_pages }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Last">
<li class="page-item"> <i data-lucide="chevrons-right" class="w-4 h-4"></i>
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
<span aria-hidden="true">&raquo;</span>
</a> </a>
</li>
{% endif %} {% endif %}
</ul>
</nav> </nav>
</div> </div>
</div>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="empty-state"> <!-- No Results -->
<i class="fas fa-inbox"></i> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
<h3 class="h5 mb-3">{% trans "No Submissions Found" %}</h3> <i data-lucide="inbox" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
<p class="text-muted mb-4"> <h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No Submissions Found" %}</h3>
{% trans "There are no submissions for this form template yet." %} <p class="text-gray-500 mb-6">{% trans "There are no submissions for this form template yet." %}</p>
</p> <a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-main-action"> <i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Submissions" %}
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
</a> </a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
</div> <script>
lucide.createIcons();
</script>
{% endblock %} {% endblock %}

View File

@ -1,279 +1,98 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n crispy_forms_tags %} {% load static i18n %}
{% block title %}{% trans "Submissions for" %} {{ template.name }} - ATS{% endblock %} {% block title %}{% trans "Submissions for" %} {{ template.name }} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* UI Variables (Matching Form Templates List) */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-gray-light: #f8f9fa;
}
/* --- Typography and Color Overrides --- */
.text-primary { color: var(--kaauh-teal) !important; }
/* --- Button Base Styles (Matching Form Templates List) --- */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 900 ;
padding: 0.375rem 0.75rem;
border-radius: 0.5rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
color: white;
}
/* Secondary Button Style (for Edit/Preview) */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Size Utilities (matching Bootstrap convention) */
.btn-lg {
padding: 0.75rem 1.5rem;
font-size: 1.1rem;
}
.btn-sm {
font-size: 0.8rem;
padding: 0.3rem 0.6rem;
}
/* --- Card and Layout Styles (Matching Form Templates List) --- */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.card-header {
background-color: var(--kaauh-teal-dark) !important;
border-bottom: 1px solid var(--kaauh-border);
color: white !important;
font-weight: 600;
padding: 1rem 1.25rem;
border-radius: 0.75rem 0.75rem 0 0;
}
.card-header h1 {
color: white !important;
font-weight: 700;
font-size: 1.5rem;
}
.card-header .fas {
color: white !important;
}
.card-header .small {
color: rgba(255, 255, 255, 0.7) !important;
}
.card-body {
padding: 1.25rem;
}
/* --- Table Styles --- */
.table-responsive {
border-radius: 0.5rem;
overflow: hidden;
}
.table {
margin-bottom: 0;
}
.table thead th {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-border);
font-weight: 600;
text-transform: uppercase;
font-size: 0.8rem;
letter-spacing: 0.5px;
padding: 1rem;
}
.table tbody td {
padding: 1rem;
vertical-align: middle;
border-color: var(--kaauh-border);
}
.table tbody tr {
transition: background-color 0.2s;
}
.table tbody tr:hover {
background-color: var(--kaauh-gray-light);
}
/* --- Pagination Styling (Matching Form Templates List) --- */
.pagination .page-item .page-link {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-border);
}
.pagination .page-item.active .page-link {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
.pagination .page-item:hover .page-link:not(.active) {
background-color: #e9ecef;
}
.pagination-info {
color: var(--kaauh-primary-text);
font-size: 0.9rem;
}
/* --- Empty State Theming --- */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--kaauh-primary-text);
border: 2px dashed var(--kaauh-border);
border-radius: 0.75rem;
background-color: var(--kaauh-gray-light);
}
.empty-state i {
font-size: 1rem;
margin-bottom: 0.5rem;
color: var(--kaauh-teal-dark);
}
.empty-state .btn-main-action .fas {
color: white !important;
}
/* --- Breadcrumb --- */
.breadcrumb {
background-color: transparent;
padding: 0;
margin-bottom: 1rem;
}
.breadcrumb-item a {
color: var(--kaauh-teal-dark);
text-decoration: none;
}
.breadcrumb-item a:hover {
text-decoration: underline;
}
.breadcrumb-item.active {
color: var(--kaauh-primary-text);
}
</style>
{% endblock %}
{% block content %} {% block content %}
{% comment %} <div class="container py-4"> <div class="space-y-6">
<!-- Search and Filter Section -->
<div class="card shadow-sm mb-4"> <!-- Desktop Header -->
<div class="card-body"> <div class="hidden lg:block">
<form method="get" action="" class="w-100"> <!-- Breadcrumb -->
<div class="row g-3 align-items-end"> <nav class="mb-6" aria-label="breadcrumb">
<div class="col-12 col-md-5"> <ol class="flex items-center gap-2 text-sm flex-wrap">
<label for="search" class="form-label small text-muted">{% trans "Search by name or Email" %}</label> <li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
<div class="input-group"> <i data-lucide="home" class="w-4 h-4"></i> {% trans "Dashboard" %}
{% include 'includes/search_form.html' %} </a></li>
</div> <li class="text-gray-400">/</li>
</div> <li><a href="{% url 'form_templates_list' %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Form Templates" %}</a></li>
<div class="col-12 col-md-2"> <li class="text-gray-400">/</li>
<label for="date_from" class="form-label small text-muted">{% trans "From Date" %}</label> <li class="text-temple-red font-semibold">{% trans "Submissions" %}</li>
<input type="date" class="form-control" id="date_from" name="date_from" value="{{ request.GET.date_from }}">
</div>
<div class="col-12 col-md-2">
<label for="date_to" class="form-label small text-muted">{% trans "To Date" %}</label>
<input type="date" class="form-control" id="date_to" name="date_to" value="{{ request.GET.date_to }}">
</div>
<div class="col-12 col-md-3 d-flex gap-2">
<button type="submit" class="btn btn-main-action flex-grow-1">
<i class="fas fa-search me-1"></i> {% trans "Filter" %}
</button>
<a href="?" class="btn btn-outline-secondary flex-grow-1">
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
</a>
</div>
</div>
</form>
</div>
</div>
{% endcomment %}
<div class="container py-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">{% trans "Dashboard" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary">{% trans "Form Templates" %}</a></li>
<li class="breadcrumb-item active" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">{% trans "Submissions" %}</li>
</ol> </ol>
</nav> </nav>
<div class="card shadow-sm"> <div class="flex justify-between items-start mb-6">
<div class="card-header d-flex justify-content-between align-items-center"> <div class="flex-1">
<div> <h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
<h1 class="h3 mb-1 d-flex align-items-center"> <div class="bg-temple-red/10 p-3 rounded-xl">
<i class="fas fa-file-alt me-2"></i> <i data-lucide="file-text" class="w-8 h-8 text-temple-red"></i>
{% trans "Submissions for" %}: <span class="text-white ms-2">{{ template.name }}</span> </div>
{% trans "Submissions for" %}: {{ template.name }}
</h1> </h1>
<small class="text-white-50">Template ID: #{{ template.id }}</small> <p class="text-sm text-gray-500 mt-2">Template ID: #{{ template.id }}</p>
</div> </div>
<div class="d-flex gap-2"> <div class="flex gap-2">
<a href="{% url 'form_template_all_submissions' template.id %}" class="btn btn-outline-light btn-sm"> <a href="{% url 'form_template_all_submissions' template.id %}" class="inline-flex items-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-4 py-2.5 rounded-xl text-sm transition font-medium">
<i class="fas fa-table me-1"></i> {% trans "View All in Table" %} <i data-lucide="table" class="w-4 h-4"></i> {% trans "View All in Table" %}
</a> </a>
<a href="{% url 'form_templates_list' %}" class="btn btn-outline-light btn-sm"> <a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Templates" %} <i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Templates" %}
</a> </a>
</div> </div>
</div> </div>
<div class="card-body"> </div>
<!-- Mobile Header -->
<div class="lg:hidden mb-4">
<div class="flex items-center justify-between mb-4">
<h1 class="text-xl font-bold text-gray-900 flex items-center gap-2">
<div class="bg-temple-red/10 p-2 rounded-lg">
<i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
</div>
{% trans "Submissions" %}
</h1>
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back" %}
</a>
</div>
</div>
{% if page_obj.object_list %} {% if page_obj.object_list %}
<div id="form-template-submissions-list"> <div id="form-template-submissions-list">
{# View Switcher #} {# View Switcher #}
{% include "includes/_list_view_switcher.html" with list_id="form-template-submissions-list" %} {% include "includes/_list_view_switcher.html" with list_id="form-template-submissions-list" %}
{# Table View (Default) #} {# Table View (Default) #}
<div class="table-view active"> <div class="table-view active hidden lg:block bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="table-responsive mb-4"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<table class="table table-hover"> <h5 class="text-lg font-bold flex items-center gap-2">
<thead> <i data-lucide="list" class="w-5 h-5"></i>
{% trans "Submission Listings" %}
</h5>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr> <tr>
<th scope="col">{% trans "Submission ID" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Submission ID" %}</th>
<th scope="col">{% trans "Applicant Name" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Applicant Name" %}</th>
<th scope="col">{% trans "Applicant Email" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Applicant Email" %}</th>
<th scope="col">{% trans "Submitted At" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Submitted At" %}</th>
<th scope="col" class="text-end">{% trans "Actions" %}</th> <th scope="col" class="px-6 py-3 text-right text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-100">
{% for submission in page_obj %} {% for submission in page_obj %}
<tr> <tr class="hover:bg-gray-50 transition-colors">
<td class="fw-medium">{{ submission.id }}</td> <td class="px-6 py-4 font-semibold text-gray-900">{{ submission.id }}</td>
<td>{{ submission.applicant_name|default:"N/A" }}</td> <td class="px-6 py-4 text-sm text-gray-700">{{ submission.applicant_name|default:"N/A" }}</td>
<td>{{ submission.applicant_email|default:"N/A" }}</td> <td class="px-6 py-4 text-sm text-gray-700">{{ submission.applicant_email|default:"N/A" }}</td>
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td> <td class="px-6 py-4 text-sm text-gray-700">{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
<td class="text-end"> <td class="px-6 py-4 text-right">
<a href="{% url 'form_submission_details' template_id=submission.template.id slug=submission.slug %}" class="btn btn-sm btn-outline-primary"> <a href="{% url 'form_submission_details' template_id=submission.template.id slug=submission.slug %}"
<i class="fas fa-eye me-1"></i> {% trans "View Details" %} class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2 rounded-lg text-sm transition">
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Details" %}
</a> </a>
</td> </td>
</tr> </tr>
@ -284,92 +103,80 @@
</div> </div>
{# Card View #} {# Card View #}
<div class="card-view"> <div class="card-view lg:hidden grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="row g-4">
{% for submission in page_obj %} {% for submission in page_obj %}
<div class="col-md-6 col-lg-6"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="card h-100"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<div class="card-header"> <h3 class="h5 font-bold mb-1">{% trans "Submission" %} #{{ submission.id }}</h3>
<h3 class="h5 mb-2">{% trans "Submission" %} #{{ submission.id }}</h3> <small class="text-white/70">{{ template.name }}</small>
<small class="text-white-50">{{ template.name }}</small>
</div> </div>
<div class="card-body"> <div class="p-4 space-y-2">
<p class="card-text"> <p class="text-sm text-gray-700">
<strong>{% trans "Applicant Name" %}:</strong> {{ submission.applicant_name|default:"N/A" }}<br> <span class="font-semibold">{% trans "Applicant Name" %}:</span> {{ submission.applicant_name|default:"N/A" }}<br>
<strong>{% trans "Applicant Email" %}:</strong> {{ submission.applicant_email|default:"N/A" }}<br> <span class="font-semibold">{% trans "Applicant Email" %}:</span> {{ submission.applicant_email|default:"N/A" }}<br>
<strong>{% trans "Submitted At" %}:</strong> {{ submission.submitted_at|date:"M d, Y H:i" }} <span class="font-semibold">{% trans "Submitted At" %}:</span> {{ submission.submitted_at|date:"M d, Y H:i" }}
</p> </p>
</div> </div>
<div class="card-footer"> <div class="p-4 pt-0">
<a href="{% url 'form_submission_details' template_id=template.id slug=submission.slug %}" class="btn btn-sm btn-outline-primary w-100"> <a href="{% url 'form_submission_details' template_id=template.id slug=submission.slug %}"
<i class="fas fa-eye me-1"></i> {% trans "View Details" %} class="w-full inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2.5 rounded-xl text-sm transition font-medium">
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Details" %}
</a> </a>
</div> </div>
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div>
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
<div class="pagination-info mb-3 mb-md-0"> <div class="flex flex-col md:flex-row justify-between items-center gap-4">
<div class="text-sm text-gray-600">
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %} {% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
Showing {{ start }} to {{ end }} of {{ total }} results. Showing {{ start }} to {{ end }} of {{ total }} results.
{% endblocktrans %} {% endblocktrans %}
</div> </div>
<nav aria-label="Page navigation"> <nav aria-label="Page navigation" class="flex items-center gap-2">
<ul class="pagination pagination-sm mb-0">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item"> <a href="?page=1" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="First">
<a class="page-link" href="?page=1" aria-label="First"> <i data-lucide="chevrons-left" class="w-4 h-4"></i>
<span aria-hidden="true">&laquo;</span>
</a> </a>
</li> <a href="?page={{ page_obj.previous_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Previous">
<li class="page-item"> <i data-lucide="chevron-left" class="w-4 h-4"></i>
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
<span aria-hidden="true">&lsaquo;</span>
</a> </a>
</li>
{% endif %} {% endif %}
<li class="page-item active"> <span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
<span class="page-link">
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }} {% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
</span> </span>
</li>
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="page-item"> <a href="?page={{ page_obj.next_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Next">
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next"> <i data-lucide="chevron-right" class="w-4 h-4"></i>
<span aria-hidden="true">&rsaquo;</span>
</a> </a>
</li> <a href="?page={{ page_obj.paginator.num_pages }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Last">
<li class="page-item"> <i data-lucide="chevrons-right" class="w-4 h-4"></i>
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
<span aria-hidden="true">&raquo;</span>
</a> </a>
</li>
{% endif %} {% endif %}
</ul>
</nav> </nav>
</div> </div>
</div>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="empty-state"> <!-- No Results -->
<i class="fas fa-inbox"></i> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
<h3 class="h5 mb-3">{% trans "No Submissions Found" %}</h3> <i data-lucide="inbox" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
<p class="text-muted mb-4"> <h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No Submissions Found" %}</h3>
{% trans "There are no submissions for this form template yet." %} <p class="text-gray-500 mb-6">{% trans "There are no submissions for this form template yet." %}</p>
</p> <a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
<a href="{% url 'form_templates_list' %}" class="btn btn-main-action"> <i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Templates" %}
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Templates" %}
</a> </a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
</div> <script>
lucide.createIcons();
</script>
{% endblock %} {% endblock %}

View File

@ -4,191 +4,109 @@
{% block title %}{% trans "Form Templates" %} - {{ block.super }}{% endblock %} {% block title %}{% trans "Form Templates" %} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* UI Variables (Matching Standard Theme) */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-gray-light: #f8f9fa;
}
/* --- Typography and Color Overrides --- */
.text-primary { color: var(--kaauh-teal) !important; }
/* --- Button Base Styles (Consistent) --- */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 1rem;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: none; /* Removed translate to match other lists */
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
color: white;
}
/* Secondary Button Style (for Edit/Preview) */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Primary Outline for View/Preview */
.btn-outline-primary {
color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
}
.btn-outline-primary:hover {
background-color: var(--kaauh-teal);
color: white;
}
/* --- Card and Layout Styles (Consistent) --- */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
transition: transform 0.2s, box-shadow 0.2s;
}
.card:not(.no-hover):hover {
transform: translateY(-2px);
box-shadow: 0 6px 16px rgba(0,0,0,0.1) !important;
}
.card.no-hover:hover {
transform: none;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
/* Card Header (For Search/Filter Card) */
.card-header {
font-weight: 600;
padding: 1.25rem;
border-bottom: 1px solid var(--kaauh-border);
background-color: var(--kaauh-gray-light);
}
/* Stats Theming */
.stat-value {
font-size: 1.5rem;
font-weight: 800;
color: var(--kaauh-teal-dark);
}
.stat-label {
font-size: 0.85rem;
color: var(--kaauh-primary-text);
font-weight: 500;
}
/* Table Styling (Consistent) */
.table-view .table thead th {
background-color: var(--kaauh-teal-dark);
color: white;
font-weight: 600;
border-color: var(--kaauh-border);
text-transform: uppercase;
font-size: 0.8rem;
letter-spacing: 0.5px;
padding: 1rem;
}
.table-view .table tbody td {
vertical-align: middle;
padding: 1rem;
border-color: var(--kaauh-border);
}
.table-view .table tbody tr:hover {
background-color: var(--kaauh-gray-light);
}
/* Pagination Styling (Consistent) */
.pagination .page-item .page-link {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-border);
}
.pagination .page-item.active .page-link {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
.pagination .page-item:hover .page-link:not(.active) {
background-color: #e9ecef;
}
/* Empty State Icon Color */
.empty-state i, .text-center i.fa-3x {
color: var(--kaauh-teal-dark) !important;
}
/* Filter Buttons Container */
.filter-buttons {
display: flex;
gap: 0.5rem;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="space-y-6">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;"> <!-- Desktop Header -->
<i class="fas fa-file-alt me-2"></i>{% trans "Form Templates" %} <div class="hidden lg:block">
<!-- Breadcrumb -->
<nav class="mb-6" aria-label="breadcrumb">
<ol class="flex items-center gap-2 text-sm flex-wrap">
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Home" %}
</a></li>
<li class="text-gray-400">/</li>
<li class="text-temple-red font-semibold">{% trans "Form Templates" %}</li>
</ol>
</nav>
<div class="flex justify-between items-start mb-6">
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
<div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="file-text" class="w-8 h-8 text-temple-red"></i>
</div>
{% trans "Form Templates" %}
</h1> </h1>
<button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#createTemplateModal"> <button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md"
<i class="fas fa-plus me-1"></i> {% trans "Create New Template" %} onclick="document.getElementById('createTemplateModal').classList.remove('hidden'); document.getElementById('createTemplateModal').classList.add('flex');">
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create New Template" %}
</button> </button>
</div> </div>
{# Search/Filter Area - Matching Standard Structure #} <!-- Desktop Filters -->
<div class="card mb-4 shadow-sm no-hover"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden mb-6">
<div class="card-body"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<form method="get" class="row g-3 align-items-end"> <h5 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="filter" class="w-5 h-5"></i>
<div class="col-md-6"> {% trans "Filter Templates" %}
<label for="searchInput" class="form-label small text-muted">{% trans "Search by Template Name" %}</label> </h5>
<div class="input-group input-group-lg"> </div>
<span class="input-group-text"><i class="fas fa-search text-muted"></i></span> <div class="p-6">
<input type="text" name="q" id="searchInput" class="form-control form-control-search" <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<label for="searchInput" class="block text-xs font-semibold text-gray-600 mb-2">{% trans "Search by Template Name" %}</label>
<form method="get" class="flex gap-3">
<div class="relative flex-1">
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i>
<input type="text" name="q" id="searchInput"
class="w-full pl-10 pr-4 py-3 bg-white 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 'Search templates by name...' %}" placeholder="{% trans 'Search templates by name...' %}"
value="{{ query|default_if_none:'' }}"> value="{{ query|default_if_none:'' }}">
</div> </div>
</div> <button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-5 py-2.5 rounded-xl text-sm transition whitespace-nowrap">
<i data-lucide="filter" class="w-4 h-4"></i> {% trans "Search" %}
<div class="col-md-6">
<div class="filter-buttons">
<button type="submit" class="btn btn-main-action btn-lg">
<i class="fas fa-filter me-1"></i> {% trans "Search" %}
</button> </button>
{# Show Clear button if search is active #}
{% if query %} {% if query %}
<a href="{% url 'form_templates_list' %}" class="btn btn-outline-secondary btn-lg"> <a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition whitespace-nowrap">
<i class="fas fa-times me-1"></i> {% trans "Clear Search" %} <i data-lucide="x" class="w-4 h-4"></i> {% trans "Clear" %}
</a> </a>
{% endif %} {% endif %}
</form>
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- Mobile Header -->
<div class="lg:hidden mb-4">
<div class="flex items-center justify-between mb-4">
<h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
<div class="bg-temple-red/10 p-2 rounded-lg">
<i data-lucide="file-text" class="w-6 h-6 text-temple-red"></i>
</div>
{% trans "Form Templates" %}
</h1>
<button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl text-sm transition shadow-sm"
onclick="document.getElementById('createTemplateModal').classList.remove('hidden'); document.getElementById('createTemplateModal').classList.add('flex');">
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create" %}
</button>
</div>
<!-- Mobile Filters -->
<div class="space-y-3">
<div>
<label for="searchInputMobile" class="block text-xs font-semibold text-gray-600 mb-2">{% trans "Search Templates" %}</label>
<form method="get" class="flex gap-2">
<div class="relative flex-1">
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i>
<input type="text" name="q" id="searchInputMobile"
class="w-full pl-10 pr-4 py-3 bg-white 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 'Search...' %}"
value="{{ query|default_if_none:'' }}">
</div>
<button type="submit" class="bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-3 rounded-xl transition">
<i data-lucide="search" class="w-4 h-4"></i>
</button>
{% if query %}
<a href="{% url 'form_templates_list' %}" class="bg-gray-100 hover:bg-gray-200 text-gray-600 px-4 py-3 rounded-xl transition">
<i data-lucide="x" class="w-4 h-4"></i>
</a>
{% endif %}
</form> </form>
</div> </div>
</div> </div>
</div>
{% if templates %} {% if templates %}
<div id="form-templates-list"> <div id="form-templates-list">
@ -196,110 +114,108 @@
{% include "includes/_list_view_switcher.html" with list_id="form-templates-list" %} {% include "includes/_list_view_switcher.html" with list_id="form-templates-list" %}
{# Card View (Default) #} {# Card View (Default) #}
<div class="card-view active row g-4"> <div class="card-view active lg:hidden grid grid-cols-1 gap-4">
{% for template in templates %} {% for template in templates %}
<div class="col-lg-4 col-md-6"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="card template-card h-100 shadow-sm"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<div class="card-body d-flex flex-column"> <h5 class="font-bold text-lg">{{ template.name }}</h5>
<h5 class="card-title fw-bold" style="color: var(--kaauh-teal-dark);">{{ template.name }}</h5> <span class="text-sm text-white/80">
<span class="text-muted small mb-3"> <i data-lucide="briefcase" class="w-4 h-4 inline mr-1"></i> {{ template.job|default:"N/A" }}
<i class="fas fa-briefcase me-1"></i> {{ template.job|default:"N/A" }}
</span> </span>
{# Stats #}
<div class="row text-center mb-3">
<div class="col-6 border-end">
<div class="stat-value">{{ template.get_stage_count }}</div>
<div class="stat-label">{% trans "Stages" %}</div>
</div> </div>
<div class="col-6"> <div class="p-4 space-y-3">
<div class="stat-value">{{ template.get_field_count }}</div> {# Stats #}
<div class="stat-label">{% trans "Fields" %}</div> <div class="grid grid-cols-2 gap-2 text-center mb-3">
<div class="p-3 bg-gray-50 rounded-xl">
<div class="text-2xl font-bold text-temple-red">{{ template.get_stage_count }}</div>
<div class="text-[10px] uppercase text-gray-500 font-semibold">{% trans "Stages" %}</div>
</div>
<div class="p-3 bg-gray-50 rounded-xl">
<div class="text-2xl font-bold text-temple-red">{{ template.get_field_count }}</div>
<div class="text-[10px] uppercase text-gray-500 font-semibold">{% trans "Fields" %}</div>
</div> </div>
</div> </div>
{# Description #} {# Description #}
<p class="card-text small text-muted flex-grow-1"> <p class="text-sm text-gray-600">
{% if template.description %} {% if template.description %}
{{ template.description|truncatewords:20 }} {{ template.description|truncatewords:20 }}
{% else %} {% else %}
<em class="text-muted">{% trans "No description provided" %}</em> <em class="text-gray-400">{% trans "No description provided" %}</em>
{% endif %} {% endif %}
</p> </p>
{# Action area #} {# Actions #}
<div class="mt-auto pt-2 border-top"> <div class="flex gap-2 pt-3 border-t border-gray-100">
<div class="d-flex gap-2 justify-content-end"> <a href="{% url 'application_submit_form' template.job.slug %}" class="inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition flex-1">
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "Preview" %}
<a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'Preview' %}">
<i class="fas fa-eye"></i>
</a> </a>
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}"> <a href="{% url 'form_builder' template.slug %}" class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-gray-300 hover:bg-gray-100 text-gray-600 transition">
<i class="fas fa-edit"></i> <i data-lucide="edit-2" class="w-4 h-4"></i>
</a> </a>
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Submissions' %}"> <a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-gray-300 hover:bg-gray-100 text-gray-600 transition">
<i class="fas fa-file-alt"></i> <i data-lucide="file-text" class="w-4 h-4"></i>
</a> </a>
<button type="button" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}" <button type="button" class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-red-300 bg-red-50 hover:bg-red-100 text-red-600 transition"
data-bs-toggle="modal" data-bs-target="#deleteModal" onclick="deleteTemplate('{{ template.name }}', '#')">
data-delete-url="#" <i data-lucide="trash-2" class="w-4 h-4"></i>
data-item-name="{{ template.name }}">
<i class="fas fa-trash-alt"></i>
</button> </button>
</div> </div>
</div> </div>
</div> <div class="bg-gray-50 text-gray-500 text-xs p-3 flex justify-between">
<div class="card-footer bg-light text-muted small"> <span><i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i> {% trans "Created:" %} {{ template.created_at|date:"M d, Y" }}</span>
<div class="d-flex justify-content-between"> <span><i data-lucide="refresh-cw" class="w-3 h-3 inline mr-1"></i> {{ template.updated_at|timesince }} {% trans "ago" %}</span>
<span><i class="fas fa-calendar-alt me-1"></i> {% trans "Created:" %} {{ template.created_at|date:"M d, Y" }}</span>
<span><i class="fas fa-sync-alt me-1"></i> {{ template.updated_at|timesince }} {% trans "ago" %}</span>
</div>
</div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{# Table View #} {# Table View #}
<div class="table-view"> <div class="hidden lg:block bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="table-responsive"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<table class="table table-hover align-middle mb-0"> <h5 class="text-lg font-bold flex items-center gap-2">
<thead> <i data-lucide="list" class="w-5 h-5"></i>
{% trans "Template Listings" %}
</h5>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr> <tr>
<th scope="col" style="width: 30%;">{% trans "Template Name" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Template Name" %}</th>
<th scope="col" style="width: 15%;">{% trans "Job" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Job" %}</th>
<th scope="col" style="width: 8%;">{% trans "Stages" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Stages" %}</th>
<th scope="col" style="width: 8%;">{% trans "Fields" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Fields" %}</th>
<th scope="col" style="width: 15%;">{% trans "Created" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Created" %}</th>
<th scope="col" style="width: 15%;">{% trans "Last Updated" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Last Updated" %}</th>
<th scope="col" style="width: 9%;" class="text-end">{% trans "Actions" %}</th> <th scope="col" class="px-6 py-3 text-center text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-100">
{% for template in templates %} {% for template in templates %}
<tr> <tr class="hover:bg-gray-50 transition-colors">
<td class="fw-medium text-primary">{{ template.name }}</td> <td class="px-6 py-4">
<td>{{ template.job|default:"N/A" }}</td> <a href="#" class="text-temple-red font-semibold hover:text-[#7a1a29] transition-colors">{{ template.name }}</a>
<td>{{ template.get_stage_count }}</td> </td>
<td>{{ template.get_field_count }}</td> <td class="px-6 py-4 text-sm text-gray-700">{{ template.job|default:"N/A" }}</td>
<td>{{ template.created_at|date:"M d, Y" }}</td> <td class="px-6 py-4 text-sm text-gray-700">{{ template.get_stage_count }}</td>
<td>{{ template.updated_at|date:"M d, Y" }}</td> <td class="px-6 py-4 text-sm text-gray-700">{{ template.get_field_count }}</td>
<td class="text-end"> <td class="px-6 py-4 text-sm text-gray-700">{{ template.created_at|date:"M d, Y" }}</td>
<div class="btn-group btn-group-sm" role="group"> <td class="px-6 py-4 text-sm text-gray-700">{{ template.updated_at|date:"M d, Y" }}</td>
<a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary" title="{% trans 'Preview' %}"> <td class="px-6 py-4 text-center">
<i class="fas fa-eye"></i> <div class="flex items-center justify-center gap-2">
<a href="{% url 'application_submit_form' template.job.slug %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-temple-red/10 hover:bg-temple-red/20 text-temple-red transition" title="{% trans 'Preview' %}">
<i data-lucide="eye" class="w-4 h-4"></i>
</a> </a>
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}"> <a href="{% url 'form_builder' template.slug %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-600 transition" title="{% trans 'Edit' %}">
<i class="fas fa-edit"></i> <i data-lucide="edit-2" class="w-4 h-4"></i>
</a> </a>
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Submissions' %}"> <a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-600 transition" title="{% trans 'Submissions' %}">
<i class="fas fa-file-alt"></i> <i data-lucide="file-text" class="w-4 h-4"></i>
</a> </a>
<button type="button" class="btn btn-outline-danger" title="{% trans 'Delete' %}" <button type="button" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-red-300 bg-red-50 hover:bg-red-100 text-red-600 transition" title="{% trans 'Delete' %}"
data-bs-toggle="modal" data-bs-target="#deleteModal" onclick="deleteTemplate('{{ template.name }}', '#')">
data-delete-url="#" <i data-lucide="trash-2" class="w-4 h-4"></i>
data-item-name="{{ template.name }}">
<i class="fas fa-trash-alt"></i>
</button> </button>
</div> </div>
</td> </td>
@ -311,80 +227,98 @@
</div> </div>
</div> </div>
{# Pagination (Standardized) #} {# Pagination #}
{% if templates.has_other_pages %} {% if templates.has_other_pages %}
<nav aria-label="Page navigation" class="mt-4"> <nav aria-label="Page navigation" class="flex justify-center items-center gap-2">
<ul class="pagination justify-content-center">
{% if templates.has_previous %} {% if templates.has_previous %}
<li class="page-item"> <a href="?page=1{% if query %}&q={{ query }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition">
<a class="page-link" href="?page=1{% if query %}&q={{ query }}{% endif %}">First</a> {% trans "First" %}
</li> </a>
<li class="page-item"> <a href="?page={{ templates.previous_page_number }}{% if query %}&q={{ query }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition">
<a class="page-link" href="?page={{ templates.previous_page_number }}{% if query %}&q={{ query }}{% endif %}">Previous</a> {% trans "Previous" %}
</li> </a>
{% endif %} {% endif %}
<li class="page-item active"> <span class="px-3 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
<span class="page-link">{{ templates.number }} of {{ templates.paginator.num_pages }}</span> {{ templates.number }} {% trans "of" %} {{ templates.paginator.num_pages }}
</li> </span>
{% if templates.has_next %} {% if templates.has_next %}
<li class="page-item"> <a href="?page={{ templates.next_page_number }}{% if query %}&q={{ query }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition">
<a class="page-link" href="?page={{ templates.next_page_number }}{% if query %}&q={{ query }}{% endif %}">Next</a> {% trans "Next" %}
</li> </a>
<li class="page-item"> <a href="?page={{ templates.paginator.num_pages }}{% if query %}&q={{ query }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition">
<a class="page-link" href="?page={{ templates.paginator.num_pages }}{% if query %}&q={{ query }}{% endif %}">Last</a> {% trans "Last" %}
</li> </a>
{% endif %} {% endif %}
</ul>
</nav> </nav>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="text-center py-5 card shadow-sm"> <!-- No Results -->
<div class="card-body"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
<i class="fas fa-file-contract fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i> <i data-lucide="file-text" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
<h3 class="h4 mb-3">{% trans "No Form Templates Found" %}</h3> <h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No Form Templates Found" %}</h3>
<p class="text-muted"> <p class="text-gray-500 mb-6">
{% if query %} {% if query %}
{% blocktrans with query=query %}No templates match your search "{{ query }}".{% endblocktrans %} {% blocktrans with query=query %}No templates match your search "{{ query }}".{% endblocktrans %}
{% else %} {% else %}
{% trans "You haven't created any form templates yet." %} {% trans "You haven't created any form templates yet." %}
{% endif %} {% endif %}
</p> </p>
<button type="button" class="btn btn-main-action mt-3" data-bs-toggle="modal" data-bs-target="#createTemplateModal"> <button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md"
<i class="fas fa-plus me-1"></i> {% trans "Create Your First Template" %} onclick="document.getElementById('createTemplateModal').classList.remove('hidden'); document.getElementById('createTemplateModal').classList.add('flex');">
<i data-lucide="plus" class="w-5 h-5"></i> {% trans "Create Your First Template" %}
</button> </button>
</div> </div>
</div>
{% endif %} {% endif %}
</div> </div>
{% include 'includes/delete_modal.html' %} {% include 'includes/delete_modal.html' %}
<div class="modal fade" id="createTemplateModal" tabindex="-1" aria-labelledby="createTemplateModalLabel" aria-hidden="true"> <!-- Create Template Modal -->
<div class="modal-dialog modal-lg"> <div id="createTemplateModal" class="fixed inset-0 z-50 hidden">
<div class="modal-content"> <div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeCreateModal()"></div>
<div class="modal-header bg-light"> <div class="relative min-h-screen flex items-center justify-center p-4">
<h5 class="modal-title" id="createTemplateModalLabel"> <div class="relative bg-white rounded-2xl shadow-2xl w-full max-w-lg">
<i class="fas fa-file-alt me-2"></i>{% trans "Create New Form Template" %} <div class="bg-gray-50 px-6 py-4 border-b border-gray-200 rounded-t-2xl">
</h5> <h3 class="text-lg font-bold text-gray-900 flex items-center gap-2">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
{% trans "Create New Form Template" %}
</h3>
<button type="button" onclick="closeCreateModal()" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition">
<i data-lucide="x" class="w-6 h-6"></i>
</button>
</div> </div>
<div class="modal-body"> <div class="p-6">
{% url 'create_form_template' as create_form_template_url %}
<form id="createTemplateForm" method="post" action="{% url 'create_form_template' %}"> <form id="createTemplateForm" method="post" action="{% url 'create_form_template' %}">
{% csrf_token %} {% csrf_token %}
{{form|crispy}} {{form|crispy}}
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="px-6 py-4 border-t border-gray-200 flex gap-3 justify-end">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button> <button type="button" onclick="closeCreateModal()" class="px-4 py-2 border border-gray-300 text-gray-700 rounded-xl hover:bg-gray-50 transition text-sm">
<button type="submit" form="createTemplateForm" class="btn btn-main-action"> {% trans "Cancel" %}
<i class="fas fa-save me-1"></i>{% trans "Create Template" %} </button>
<button type="submit" form="createTemplateForm" class="px-4 py-2 bg-temple-red hover:bg-[#7a1a29] text-white rounded-xl transition text-sm font-semibold flex items-center gap-2">
<i data-lucide="save" class="w-4 h-4"></i> {% trans "Create Template" %}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<script>
lucide.createIcons();
function closeCreateModal() {
document.getElementById('createTemplateModal').classList.add('hidden');
document.getElementById('createTemplateModal').classList.remove('flex');
}
function deleteTemplate(name, url) {
if (confirm('{% trans "Are you sure you want to delete this template?" %}')) {
window.location.href = url;
}
}
</script>
{% endblock %} {% endblock %}

View File

@ -1,52 +1,52 @@
{% load i18n %} {% load i18n %}
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="flex justify-between items-center mb-3">
<div class="btn-group" role="group"> <div class="inline-flex rounded-lg overflow-hidden" role="group">
<button type="button" class="btn btn-outline-primary btn-sm view-toggle active" data-view="table" data-list-id="{{ list_id }}"> <button type="button" class="view-toggle active inline-flex items-center gap-2 px-4 py-2 text-sm font-medium border border-r-0 transition" data-view="table" data-list-id="{{ list_id }}">
<i class="fas fa-table me-1"></i> {% trans "Table" %} <i data-lucide="table" class="w-4 h-4"></i> {% trans "Table" %}
</button> </button>
<button type="button" class="btn btn-outline-primary btn-sm view-toggle" data-view="card" data-list-id="{{ list_id }}"> <button type="button" class="view-toggle inline-flex items-center gap-2 px-4 py-2 text-sm font-medium border border-l-0 transition" data-view="card" data-list-id="{{ list_id }}">
<i class="fas fa-th me-1"></i> {% trans "Card" %} <i data-lucide="layout-grid" class="w-4 h-4"></i> {% trans "Card" %}
</button> </button>
</div> </div>
</div> </div>
<style> <style>
/* --- View Switcher Styles (Consolidated & Corrected) --- */
/* View Toggle Styles */ /* View Toggle Styles */
.view-toggle { .view-toggle {
border-radius: 0.25rem; border-color: #d1d5db;
margin-right: 0.25rem; color: #4b5563;
background-color: white;
}
.view-toggle:hover {
background-color: #f3f4f6;
} }
.view-toggle.active { .view-toggle.active {
background-color: var(--kaauh-teal); background-color: #b91c1c;
border-color: var(--kaauh-teal); border-color: #b91c1c;
color: white; color: white;
} }
.view-toggle.active:hover { .view-toggle.active:hover {
background-color: var(--kaauh-teal-dark); background-color: #991b1b;
border-color: var(--kaauh-teal-dark);
} }
/* Hide elements by default */ /* Hide elements by default */
.table-view, .table-view,
.card-view { .card-view {
display: none !important; /* Use !important to ensure hiding works */ display: none;
} }
/* Show active view */ /* Show active view */
.table-view.active { .table-view.active {
display: block !important; display: block;
} }
.card-view.active { .card-view.active {
/* Rely on the 'row' class which uses display: flex for proper column alignment. */ display: flex;
display: flex !important; /* rows often use display: flex in Bootstrap */ flex-wrap: wrap;
flex-wrap: wrap !important;
} }
/* Card View Styles */ /* Card View Styles */
.card-view .card { .card-view .card {
border: 1px solid var(--kaauh-border); border: 1px solid #e5e7eb;
border-radius: 0.75rem; border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06); box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white; background-color: white;
@ -56,7 +56,7 @@
flex-direction: column; flex-direction: column;
} }
.card-view .card-header { .card-view .card-header {
background-color: var(--kaauh-teal-dark); background-color: #b91c1c;
color: white; color: white;
font-weight: 600; font-weight: 600;
padding: 1rem 1.25rem; padding: 1rem 1.25rem;
@ -67,24 +67,20 @@
flex-grow: 1; flex-grow: 1;
} }
.card-view .card-title { .card-view .card-title {
color: var(--kaauh-teal-dark); color: #b91c1c;
font-weight: 700; font-weight: 700;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
.card-view .card-text { .card-view .card-text {
color: var(--kaauh-primary-text); color: #374151;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.card-view .card-footer { .card-view .card-footer {
padding: 0.75rem 1.25rem; padding: 0.75rem 1.25rem;
background-color: #f8f9fa; background-color: #f8f9fa;
border-top: 1px solid var(--kaauh-border); border-top: 1px solid #e5e7eb;
border-radius: 0 0 0.75rem 0.75rem; border-radius: 0 0 0.75rem 0.75rem;
} }
.card-view .btn-sm {
font-size: 0.8rem;
padding: 0.3rem 0.6rem;
}
/* Table View Styles */ /* Table View Styles */
.table-view .table-responsive { .table-view .table-responsive {
@ -93,48 +89,43 @@
} }
.table-view .table { .table-view .table {
margin-bottom: 0; margin-bottom: 0;
width: 100%;
} }
.table-view .table thead th { .table-view .table thead th {
background-color: var(--kaauh-teal-dark); background-color: #b91c1c;
color: white; color: white;
border-color: var(--kaauh-border); border-color: #e5e7eb;
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
font-size: 0.8rem; font-size: 0.8rem;
letter-spacing: 0.5px; letter-spacing: 0.5px;
padding: 1rem; padding: 1rem;
text-align: left;
} }
.table-view .table tbody td { .table-view .table tbody td {
padding: 1rem; padding: 1rem;
vertical-align: middle; vertical-align: middle;
border-color: var(--kaauh-border); border-color: #e5e7eb;
} }
.table-view .table tbody tr { .table-view .table tbody tr {
transition: background-color 0.2s; transition: background-color 0.2s;
} }
/* NOTE: We need to assume var(--kaauh-gray-light) is defined in base.html or job_list.html */
.table-view .table tbody tr:hover { .table-view .table tbody tr:hover {
background-color: #f0f0f0; /* Fallback color for hover */ background-color: #f9fafb;
} }
</style> </style>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const listContainer = document.getElementById('{{ list_id }}'); const listContainer = document.getElementById('{{ list_id }}');
if (!listContainer) return; // Exit if container isn't found if (!listContainer) return;
// Get list ID from the data attribute (or use the variable from Django context if the button is loaded)
const listId = listContainer.id; const listId = listContainer.id;
// Get saved view preference from localStorage
const savedView = localStorage.getItem(`list_view_${listId}`) || 'table'; const savedView = localStorage.getItem(`list_view_${listId}`) || 'table';
// Set initial view
setView(savedView); setView(savedView);
// Add click event listeners to view toggle buttons
document.querySelectorAll('.view-toggle').forEach(button => { document.querySelectorAll('.view-toggle').forEach(button => {
// Ensure the button belongs to this list (optional check)
if (button.getAttribute('data-list-id') === listId) { if (button.getAttribute('data-list-id') === listId) {
button.addEventListener('click', function() { button.addEventListener('click', function() {
const view = this.getAttribute('data-view'); const view = this.getAttribute('data-view');
@ -144,7 +135,6 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
function setView(view) { function setView(view) {
// Update button states
document.querySelectorAll('.view-toggle[data-list-id="{{ list_id }}"]').forEach(button => { document.querySelectorAll('.view-toggle[data-list-id="{{ list_id }}"]').forEach(button => {
if (button.getAttribute('data-view') === view) { if (button.getAttribute('data-view') === view) {
button.classList.add('active'); button.classList.add('active');
@ -153,11 +143,9 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
// Update view visibility
const tableView = listContainer.querySelector('.table-view'); const tableView = listContainer.querySelector('.table-view');
const cardView = listContainer.querySelector('.card-view'); const cardView = listContainer.querySelector('.card-view');
// Apply active class to the correct container
if (view === 'table') { if (view === 'table') {
if (tableView) tableView.classList.add('active'); if (tableView) tableView.classList.add('active');
if (cardView) cardView.classList.remove('active'); if (cardView) cardView.classList.remove('active');
@ -166,8 +154,10 @@ document.addEventListener('DOMContentLoaded', function() {
if (cardView) cardView.classList.add('active'); if (cardView) cardView.classList.add('active');
} }
// Save preference to localStorage
localStorage.setItem(`list_view_${listId}`, view); localStorage.setItem(`list_view_${listId}`, view);
} }
}); });
// Initialize Lucide icons
lucide.createIcons();
</script> </script>

View File

@ -1,7 +1,35 @@
{% load i18n %} {% load i18n %}
{% url 'update_application_exam_status' slug=application.slug as url %} {% url 'update_application_exam_status' slug=application.slug as url %}
<form data-on-submit="@post('{{url}}', {contentType: 'form', headers: {'X-CSRFToken': '{{ csrf_token }}'}})"> <form method="post" action="{{ url }}" data-on-submit="@post('{{url}}', {contentType: 'form', headers: {'X-CSRFToken': '{{ csrf_token }}'}})">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} <div class="space-y-4">
<button type="submit" class="btn btn-primary">{% trans "Update" %}</button> <div>
<label for="id_exam_status" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Exam Status" %}</label>
<select name="exam_status" id="id_exam_status" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
<option value="pending" {% if application.exam_status == 'pending' %}selected{% endif %}>{% trans "Pending" %}</option>
<option value="passed" {% if application.exam_status == 'passed' %}selected{% endif %}>{% trans "Passed" %}</option>
<option value="failed" {% if application.exam_status == 'failed' %}selected{% endif %}>{% trans "Failed" %}</option>
</select>
</div>
{% if form.exam_score %}
<div>
<label for="id_exam_score" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Exam Score" %}</label>
<input type="number" name="exam_score" id="id_exam_score" value="{{ application.exam_score|default:'' }}" min="0" max="100" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
</div>
{% endif %}
{% if form.exam_notes %}
<div>
<label for="id_exam_notes" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Exam Notes" %}</label>
<textarea name="exam_notes" id="id_exam_notes" rows="3" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">{{ application.exam_notes|default:'' }}</textarea>
</div>
{% endif %}
<div>
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2.5 rounded-lg text-sm transition shadow-sm hover:shadow-md">
{% trans "Update" %}
</button>
</div>
</div>
</form> </form>

View File

@ -2,88 +2,91 @@
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% if LANGUAGE_CODE == 'en' %} {% if LANGUAGE_CODE == 'en' %}
<h5> {% trans "AI Score" %}: <span class="badge bg-success"><i class="fas fa-robot me-1"></i> {{ application.match_score }}%</span> <span class="badge bg-success"><i class="fas fa-graduation-cap me-1"></i> {{ application.professional_category }} </span></h5> <h5 class="text-lg font-bold text-gray-900 mb-4">{% trans "AI Score" %}:
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium"><i data-lucide="bot" class="w-3.5 h-3.5"></i> {{ application.match_score }}%</span>
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium"><i data-lucide="graduation-cap" class="w-3.5 h-3.5"></i> {{ application.professional_category }}</span>
</h5>
<div class="row mb-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-briefcase me-2 text-primary"></i> <i data-lucide="briefcase" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Job Fit" %}</small> <small class="text-gray-600 font-medium">{% trans "Job Fit" %}</small>
</div> </div>
<p class="mb-1">{{ application.job_fit_narrative }}</p> <p class="text-gray-700 text-sm">{{ application.job_fit_narrative }}</p>
</div> </div>
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-star me-2 text-warning"></i> <i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Top Keywords" %}</small> <small class="text-gray-600 font-medium">{% trans "Top Keywords" %}</small>
</div> </div>
<div class="d-flex flex-wrap gap-1"> <div class="flex flex-wrap gap-2">
{% for keyword in application.top_3_keywords %} {% for keyword in application.top_3_keywords %}
<span class="badge bg-info text-dark me-1">{{ keyword }}</span> <span class="inline-block px-2.5 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">{{ keyword }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-clock me-2 text-info"></i> <i data-lucide="clock" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Experience" %}</small> <small class="text-gray-600 font-medium">{% trans "Experience" %}</small>
</div> </div>
<p class="mb-1"><strong>{{ application.years_of_experience }}</strong> {% trans "years" %}</p> <p class="text-sm mb-1"><strong class="text-gray-900">{{ application.years_of_experience }}</strong> {% trans "years" %}</p>
<p class="mb-0"><strong>{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title }}</p> <p class="text-sm text-gray-700"><strong class="text-gray-900">{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title }}</p>
</div> </div>
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-chart-line me-2 text-success"></i> <i data-lucide="trending-up" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Skills" %}</small> <small class="text-gray-600 font-medium">{% trans "Skills" %}</small>
</div> </div>
<p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p> <p class="text-sm mb-1"><strong class="text-gray-900">{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
<p class="mb-0"><strong>{% trans "Industry Match:" %}</strong> <p class="text-sm"><strong class="text-gray-900">{% trans "Industry Match:" %}</strong>
<span class="badge {% if application.industry_match_score >= 70 %}bg-success{% elif application.industry_match_score >= 40 %}bg-warning{% else %}bg-danger{% endif %}"> <span class="inline-block px-2.5 py-1 rounded-full text-xs font-medium {% if application.industry_match_score >= 70 %}bg-green-100 text-green-800{% elif application.industry_match_score >= 40 %}bg-yellow-100 text-yellow-800{% else %}bg-red-100 text-red-800{% endif %}">
{{ application.industry_match_score }}% {{ application.industry_match_score }}%
</span> </span>
</p> </p>
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="message-square" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Recommendation" %}</label>
<textarea class="form-control" rows="6" readonly>{{ application.recommendation }}</textarea> <textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="6" readonly>{{ application.recommendation }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="thumbs-up" class="w-4 h-4 inline mr-1 text-green-600"></i> {% trans "Strengths" %}</label>
<textarea class="form-control" rows="4" readonly>{{ application.strengths }}</textarea> <textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="4" readonly>{{ application.strengths }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="thumbs-down" class="w-4 h-4 inline mr-1 text-red-600"></i> {% trans "Weaknesses" %}</label>
<textarea class="form-control" rows="4" readonly>{{ application.weaknesses }}</textarea> <textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="4" readonly>{{ application.weaknesses }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-list-check me-1"></i> {% trans "Criteria Assessment" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="check-square" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Criteria Assessment" %}</label>
<div class="table-responsive"> <div class="overflow-hidden rounded-lg border border-gray-200">
<table class="table table-sm"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr class="bg-temple-red text-white">
<th>{% trans "Criteria" %}</th> <th class="px-4 py-3 text-left font-semibold">{% trans "Criteria" %}</th>
<th>{% trans "Status" %}</th> <th class="px-4 py-3 text-left font-semibold">{% trans "Status" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-200">
{% for criterion, status in application.criteria_checklist.items %} {% for criterion, status in application.criteria_checklist.items %}
<tr> <tr class="hover:bg-gray-50">
<td>{{ criterion }}</td> <td class="px-4 py-3 text-gray-900">{{ criterion }}</td>
<td> <td class="px-4 py-3">
{% if status == "Met" %} {% if status == "Met" %}
<span class="badge bg-success"><i class="fas fa-check me-1"></i> {% trans "Met" %}</span> <span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i data-lucide="check" class="w-3 h-3"></i> {% trans "Met" %}</span>
{% elif status == "Not Met" %} {% elif status == "Not Met" %}
<span class="badge bg-danger"><i class="fas fa-times me-1"></i> {% trans "Not Met" %}</span> <span class="inline-flex items-center gap-1 px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium"><i data-lucide="x" class="w-3 h-3"></i> {% trans "Not Met" %}</span>
{% else %} {% else %}
<span class="badge bg-secondary">{{ status }}</span> <span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ status }}</span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -93,121 +96,124 @@
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-check-circle me-2 text-success"></i> <i data-lucide="check-circle" class="w-4 h-4 text-green-600"></i>
<small class="text-muted">{% trans "Minimum Requirements" %}</small> <small class="text-gray-600 font-medium">{% trans "Minimum Requirements" %}</small>
</div> </div>
{% if application.min_requirements_met %} {% if application.min_requirements_met %}
<span class="badge bg-success">{% trans "Met" %}</span> <span class="inline-block px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium">{% trans "Met" %}</span>
{% else %} {% else %}
<span class="badge bg-danger">{% trans "Not Met" %}</span> <span class="inline-block px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium">{% trans "Not Met" %}</span>
{% endif %} {% endif %}
</div> </div>
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-star me-2 text-warning"></i> <i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Screening Rating" %}</small> <small class="text-gray-600 font-medium">{% trans "Screening Rating" %}</small>
</div> </div>
<span class="badge bg-secondary">{{ application.screening_stage_rating }}</span> <span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ application.screening_stage_rating }}</span>
</div> </div>
</div> </div>
{% if application.language_fluency %} {% if application.language_fluency %}
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="globe" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Language Fluency" %}</label>
<div class="d-flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{% for language in application.language_fluency %} {% for language in application.language_fluency %}
<span class="badge bg-light text-dark">{{ language }}</span> <span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ language }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% else %} {% else %}
<h5> {% trans "AI Score" %}: <span class="badge bg-success"><i class="fas fa-robot me-1"></i> {{ application.match_score }}%</span> <span class="badge bg-success"><i class="fas fa-graduation-cap me-1"></i> {{ application.professional_category_ar }} </span></h5> <h5 class="text-lg font-bold text-gray-900 mb-4">{% trans "AI Score" %}:
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium"><i data-lucide="bot" class="w-3.5 h-3.5"></i> {{ application.match_score }}%</span>
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium"><i data-lucide="graduation-cap" class="w-3.5 h-3.5"></i> {{ application.professional_category_ar }}</span>
</h5>
<div class="row mb-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-briefcase me-2 text-primary"></i> <i data-lucide="briefcase" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Job Fit" %}</small> <small class="text-gray-600 font-medium">{% trans "Job Fit" %}</small>
</div> </div>
<p class="mb-1">{{ application.job_fit_narrative_ar }}</p> <p class="text-gray-700 text-sm">{{ application.job_fit_narrative_ar }}</p>
</div> </div>
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-star me-2 text-warning"></i> <i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Top Keywords" %}</small> <small class="text-gray-600 font-medium">{% trans "Top Keywords" %}</small>
</div> </div>
<div class="d-flex flex-wrap gap-1"> <div class="flex flex-wrap gap-2">
{% for keyword in application.top_3_keywords_ar %} {% for keyword in application.top_3_keywords_ar %}
<span class="badge bg-info text-dark me-1">{{ keyword }}</span> <span class="inline-block px-2.5 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">{{ keyword }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-clock me-2 text-info"></i> <i data-lucide="clock" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Experience" %}</small> <small class="text-gray-600 font-medium">{% trans "Experience" %}</small>
</div> </div>
<p class="mb-1"><strong>{{ application.years_of_experience }}</strong> {% trans "years" %}</p> <p class="text-sm mb-1"><strong class="text-gray-900">{{ application.years_of_experience }}</strong> {% trans "years" %}</p>
<p class="mb-0"><strong>{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title_ar }}</p> <p class="text-sm text-gray-700"><strong class="text-gray-900">{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title_ar }}</p>
</div> </div>
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-chart-line me-2 text-success"></i> <i data-lucide="trending-up" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Skills" %}</small> <small class="text-gray-600 font-medium">{% trans "Skills" %}</small>
</div> </div>
<p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p> <p class="text-sm mb-1"><strong class="text-gray-900">{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
<p class="mb-0"><strong>{% trans "Industry Match:" %}</strong> <p class="text-sm"><strong class="text-gray-900">{% trans "Industry Match:" %}</strong>
<span class="badge {% if application.industry_match_score >= 70 %}bg-success{% elif application.industry_match_score >= 40 %}bg-warning{% else %}bg-danger{% endif %}"> <span class="inline-block px-2.5 py-1 rounded-full text-xs font-medium {% if application.industry_match_score >= 70 %}bg-green-100 text-green-800{% elif application.industry_match_score >= 40 %}bg-yellow-100 text-yellow-800{% else %}bg-red-100 text-red-800{% endif %}">
{{ application.industry_match_score }}% {{ application.industry_match_score }}%
</span> </span>
</p> </p>
</div> </div>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="message-square" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Recommendation" %}</label>
<textarea class="form-control" rows="6" readonly>{{ application.recommendation_ar }}</textarea> <textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="6" readonly>{{ application.recommendation_ar }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="thumbs-up" class="w-4 h-4 inline mr-1 text-green-600"></i> {% trans "Strengths" %}</label>
<textarea class="form-control" rows="4" readonly>{{ application.strengths_ar }}</textarea> <textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="4" readonly>{{ application.strengths_ar }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="thumbs-down" class="w-4 h-4 inline mr-1 text-red-600"></i> {% trans "Weaknesses" %}</label>
<textarea class="form-control" rows="4" readonly>{{ application.weaknesses_ar }}</textarea> <textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="4" readonly>{{ application.weaknesses_ar }}</textarea>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-list-check me-1"></i> {% trans "Criteria Assessment" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="check-square" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Criteria Assessment" %}</label>
<div class="table-responsive"> <div class="overflow-hidden rounded-lg border border-gray-200">
<table class="table table-sm"> <table class="w-full text-sm">
<thead> <thead>
<tr> <tr class="bg-temple-red text-white">
<th>{% trans "Criteria" %}</th> <th class="px-4 py-3 text-left font-semibold">{% trans "Criteria" %}</th>
<th>{% trans "Status" %}</th> <th class="px-4 py-3 text-left font-semibold">{% trans "Status" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-200">
{% for criterion, status in application.criteria_checklist_ar.items %} {% for criterion, status in application.criteria_checklist_ar.items %}
<tr> <tr class="hover:bg-gray-50">
<td>{{ criterion }}</td> <td class="px-4 py-3 text-gray-900">{{ criterion }}</td>
<td> <td class="px-4 py-3">
{% if status == "Met" %} {% if status == "Met" %}
<span class="badge bg-success"><i class="fas fa-check me-1"></i> {% trans "Met" %}</span> <span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i data-lucide="check" class="w-3 h-3"></i> {% trans "Met" %}</span>
{% elif status == "Not Met" %} {% elif status == "Not Met" %}
<span class="badge bg-danger"><i class="fas fa-times me-1"></i> {% trans "Not Met" %}</span> <span class="inline-flex items-center gap-1 px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium"><i data-lucide="x" class="w-3 h-3"></i> {% trans "Not Met" %}</span>
{% else %} {% else %}
<span class="badge bg-secondary">{{ status }}</span> <span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ status }}</span>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -217,36 +223,40 @@
</div> </div>
</div> </div>
<div class="row mb-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-check-circle me-2 text-success"></i> <i data-lucide="check-circle" class="w-4 h-4 text-green-600"></i>
<small class="text-muted">{% trans "Minimum Requirements" %}</small> <small class="text-gray-600 font-medium">{% trans "Minimum Requirements" %}</small>
</div> </div>
{% if application.min_requirements_met_ar %} {% if application.min_requirements_met_ar %}
<span class="badge bg-success">{% trans "Met" %}</span> <span class="inline-block px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium">{% trans "Met" %}</span>
{% else %} {% else %}
<span class="badge bg-danger">{% trans "Not Met" %}</span> <span class="inline-block px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium">{% trans "Not Met" %}</span>
{% endif %} {% endif %}
</div> </div>
<div class="col-md-6"> <div class="bg-gray-50 rounded-xl p-4">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center gap-2 mb-2">
<i class="fas fa-star me-2 text-warning"></i> <i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
<small class="text-muted">{% trans "Screening Rating" %}</small> <small class="text-gray-600 font-medium">{% trans "Screening Rating" %}</small>
</div> </div>
<span class="badge bg-secondary">{{ application.screening_stage_rating_ar }}</span> <span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ application.screening_stage_rating_ar }}</span>
</div> </div>
</div> </div>
{% if application.language_fluency_ar %} {% if application.language_fluency_ar %}
<div class="mb-3"> <div class="mb-4">
<label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="globe" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Language Fluency" %}</label>
<div class="d-flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
{% for language in application.language_fluency_ar %} {% for language in application.language_fluency_ar %}
<span class="badge bg-light text-dark">{{ language }}</span> <span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ language }}</span>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
<script>
lucide.createIcons();
</script>

View File

@ -1,34 +1,35 @@
{% 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="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
<div class="d-flex justify-content-center align-items-center gap-2"> <div class="flex justify-center items-center gap-4 mb-4">
<div class="form-check d-flex align-items-center gap-2"> <label class="flex items-center gap-2 cursor-pointer">
<input class="form-check-input" type="radio" name="exam_status" id="exam_passed" value="Passed" {% if application.exam_status == 'Passed' %}checked{% endif %}> <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">
<label class="form-check-label" for="exam_passed"> <span class="text-gray-700 text-sm"><i data-lucide="check" class="w-4 h-4 inline text-green-600"></i> {% trans "Passed" %}</span>
<i class="fas fa-check me-1"></i> {% trans "Passed" %} </label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" name="exam_status" id="exam_failed" value="Failed" {% if application.exam_status == 'Failed' %}checked{% endif %} class="w-4 h-4 text-temple-red border-gray-300 focus:ring-temple-red/20">
<span class="text-gray-700 text-sm"><i data-lucide="x" class="w-4 h-4 inline text-red-600"></i> {% trans "Failed" %}</span>
</label> </label>
</div> </div>
<div class="form-check d-flex align-items-center gap-2">
<input class="form-check-input" type="radio" name="exam_status" id="exam_failed" value="Failed" {% if application.exam_status == 'Failed' %}checked{% endif %}> <div class="flex justify-center items-center mt-4 gap-4">
<label class="form-check-label" for="exam_failed"> <div class="w-24 text-right">
<i class="fas fa-times me-1"></i> {% trans "Failed" %} <label for="exam_score" class="block text-sm font-medium text-gray-600">{% trans "Exam Score" %}</label>
</label> </div>
<div class="w-24">
<input type="number" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="exam_score" name="exam_score" min="0" max="100" required value="{{ application.exam_score }}">
</div>
<div class="w-24 text-left">
</div> </div>
</div> </div>
<div class="d-flex justify-content-center align-items-center mt-3 gap-2">
<div class="w-25 text-end pe-none"> <div class="text-center mt-4">
<label for="exam_score" class="form-label small text-muted">{% trans "Exam Score" %}</label> <button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2 rounded-lg text-sm transition shadow-sm hover:shadow-md">
</div> <i data-lucide="check" class="w-4 h-4"></i> {% trans "Update" %}
<div class="w-25">
<input type="number" class="form-control form-control-sm" id="exam_score" name="exam_score" min="0" max="100" required value="{{ application.exam_score }}">
</div>
<div class="w-25 text-start ps-none">
</div>
</div>
<div class="text-center mt-3">
<button type="submit" class="btn btn-success btn-sm">
<i class="fas fa-check me-1"></i> {% trans "Update" %}
</button> </button>
</div> </div>
</form> </form>
<script>
lucide.createIcons();
</script>

View File

@ -1,10 +1,14 @@
{% load i18n %} {% load i18n %}
<div class="d-flex justify-content-center align-items-center gap-2" 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="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
<a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Passed' %}" class="btn btn-outline-secondary"> <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">
<i class="fas fa-check me-1"></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="btn btn-danger"> <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">
<i class="fas fa-times me-1"></i> {% trans "Failed" %} <i data-lucide="x" class="w-4 h-4"></i> {% trans "Failed" %}
</a> </a>
</div> </div>
<script>
lucide.createIcons();
</script>

View File

@ -1,10 +1,14 @@
{% load i18n %} {% load i18n %}
<div class="d-flex justify-content-center align-items-center gap-2" 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="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}" class="btn btn-outline-secondary"> <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 class="fas fa-check me-1"></i> {% trans "Accepted" %} <i data-lucide="check" class="w-4 h-4"></i> {% trans "Accepted" %}
</a> </a>
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Rejected' %}" class="btn btn-danger"> <a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Rejected' %}" 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">
<i class="fas fa-times me-1"></i> {% trans "Rejected" %} <i data-lucide="x" class="w-4 h-4"></i> {% trans "Rejected" %}
</a> </a>
</div> </div>
<script>
lucide.createIcons();
</script>

View File

@ -1,541 +1,209 @@
<!DOCTYPE html> {% load i18n %}
<html lang="en"> <div class="max-w-7xl mx-auto bg-white rounded-3xl overflow-hidden shadow-2xl">
<head> <!-- Header -->
<meta charset="UTF-8"> <div class="bg-gradient-to-br from-temple-red to-[#004a53] text-white p-10 relative overflow-hidden">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <div class="relative z-10">
<title>Resume - Abdullah Sami Bakhsh</title> <h1 class="text-5xl font-bold mb-2.5">
<style> {% if application.name %}{{ application.name }}{% else %}{% trans "Candidate" %}{% endif %}
:root { </h1>
--kaauh-teal: #00636e; <div class="text-xl opacity-90 mb-5">{% trans "Resume Analysis" %}</div>
--kaauh-teal-dark: #004a53; <div class="flex flex-wrap gap-5 text-base">
--kaauh-light-bg: #f9fbfd; {% if application.email %}
--kaauh-border: #eaeff3; <div class="flex items-center gap-2">
--text-primary: #2c3e50; <span class="text-lg">📱</span>
--text-secondary: #6c757d; <span>{{ application.email }}</span>
--white: #ffffff;
--danger: #dc3545;
--warning: #ffc107;
--success: #28a745;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, var(--kaauh-light-bg) 0%, #e3f2fd 100%);
color: var(--text-primary);
line-height: 1.6;
min-height: 100vh;
padding: 20px;
}
.resume-container {
max-width: 1200px;
margin: 0 auto;
background: var(--white);
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 99, 110, 0.1);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
color: var(--white);
padding: 40px;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -50%;
right: -10%;
width: 500px;
height: 500px;
background: rgba(255, 255, 255, 0.05);
border-radius: 50%;
}
.header-content {
position: relative;
z-index: 1;
}
.name {
font-size: 2.5em;
font-weight: 700;
margin-bottom: 10px;
}
.title {
font-size: 1.3em;
opacity: 0.9;
margin-bottom: 20px;
}
.contact-info {
display: flex;
flex-wrap: wrap;
gap: 20px;
font-size: 0.95em;
}
.contact-item {
display: flex;
align-items: center;
gap: 8px;
}
.main-content {
display: grid;
grid-template-columns: 1fr 350px;
gap: 30px;
padding: 40px;
}
.left-column {
display: flex;
flex-direction: column;
gap: 30px;
}
.right-column {
display: flex;
flex-direction: column;
gap: 30px;
}
.section {
background: var(--white);
border-radius: 12px;
padding: 25px;
border: 1px solid var(--kaauh-border);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.section:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 99, 110, 0.1);
}
.section-title {
font-size: 1.3em;
font-weight: 600;
color: var(--kaauh-teal);
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid var(--kaauh-teal);
display: flex;
align-items: center;
gap: 10px;
}
.summary {
line-height: 1.8;
color: var(--text-secondary);
}
.experience-item {
margin-bottom: 25px;
padding-bottom: 20px;
border-bottom: 1px solid var(--kaauh-border);
}
.experience-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.job-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 10px;
}
.job-title {
font-weight: 600;
color: var(--kaauh-teal-dark);
font-size: 1.1em;
}
.company {
color: var(--text-secondary);
font-weight: 500;
}
.job-period {
color: var(--text-secondary);
font-size: 0.9em;
white-space: nowrap;
}
.achievements {
margin-top: 10px;
padding-left: 20px;
}
.achievements li {
margin-bottom: 5px;
color: var(--text-secondary);
}
.education-item {
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid var(--kaauh-border);
}
.education-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.degree {
font-weight: 600;
color: var(--kaauh-teal-dark);
}
.institution {
color: var(--text-secondary);
font-size: 0.95em;
}
.skills-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.skill-tag {
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
color: var(--white);
padding: 6px 12px;
border-radius: 20px;
font-size: 0.85em;
font-weight: 500;
}
.language-item {
display: flex;
justify-content: space-between;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: 1px solid var(--kaauh-border);
}
.language-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.language-name {
font-weight: 500;
}
.proficiency {
color: var(--text-secondary);
font-size: 0.9em;
}
.score-card {
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
color: var(--white);
text-align: center;
padding: 30px 20px;
border-radius: 12px;
}
.match-score {
font-size: 3em;
font-weight: 700;
margin-bottom: 10px;
}
.score-label {
font-size: 1.1em;
opacity: 0.9;
}
.recommendation {
background: var(--kaauh-light-bg);
border-left: 4px solid var(--danger);
padding: 15px;
border-radius: 8px;
margin-top: 15px;
}
.recommendation-title {
font-weight: 600;
color: var(--danger);
margin-bottom: 8px;
}
.recommendation-text {
color: var(--text-secondary);
font-size: 0.95em;
}
.keyword-tag {
background: var(--kaauh-light-bg);
color: var(--kaauh-teal-dark);
padding: 4px 10px;
border-radius: 15px;
font-size: 0.85em;
font-weight: 500;
border: 1px solid var(--kaauh-border);
}
.strengths-weaknesses {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.strength-box {
background: rgba(40, 167, 69, 0.1);
border: 1px solid var(--success);
padding: 15px;
border-radius: 8px;
}
.weakness-box {
background: rgba(220, 53, 69, 0.1);
border: 1px solid var(--danger);
padding: 15px;
border-radius: 8px;
}
.box-title {
font-weight: 600;
margin-bottom: 8px;
font-size: 0.9em;
}
.strength-box .box-title {
color: var(--success);
}
.weakness-box .box-title {
color: var(--danger);
}
.box-content {
font-size: 0.9em;
color: var(--text-secondary);
}
@media (max-width: 968px) {
.main-content {
grid-template-columns: 1fr;
}
.strengths-weaknesses {
grid-template-columns: 1fr;
}
}
@media (max-width: 640px) {
.name {
font-size: 2em;
}
.main-content {
padding: 20px;
}
.section {
padding: 20px;
}
.job-header {
flex-direction: column;
gap: 5px;
}
}
</style>
</head>
<body>
<div class="resume-container">
<div class="header">
<div class="header-content">
<h1 class="name">Abdullah Sami Bakhsh</h1>
<div class="title">Head, Recruitment & Training</div>
<div class="contact-info">
<div class="contact-item">
<span>📱</span>
<span>+966 561 168 180</span>
</div> </div>
<div class="contact-item"> {% endif %}
<span>✉️</span> {% if application.phone %}
<span>Bakhsh.Abdullah@Outlook.com</span> <div class="flex items-center gap-2">
</div> <span class="text-lg">✉️</span>
<div class="contact-item"> <span>{{ application.phone }}</span>
<span>📍</span>
<span>Saudi Arabia</span>
</div> </div>
{% endif %}
{% if application.location %}
<div class="flex items-center gap-2">
<span class="text-lg">📍</span>
<span>{{ application.location }}</span>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
<div class="main-content"> <!-- Main Content -->
<div class="left-column"> <div class="grid grid-cols-1 lg:grid-cols-[1fr_350px] gap-8 p-10">
<div class="section">
<h2 class="section-title"> <!-- Left Column -->
<span>📋</span> Professional Summary <div class="flex flex-col gap-8">
<!-- Professional Summary -->
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
<span>📋</span> {% trans "Professional Summary" %}
</h2> </h2>
<p class="summary"> <p class="text-gray-600 leading-8">
Strategic and resultsdriven HR leader with over 11 years of experience in human resources, specializing in business partnering, workforce planning, talent acquisition, training & development, and organizational effectiveness. {% if application.ai_analysis_data %}
{{ application.ai_analysis_data.parsed_summary }}
{% elif application.parsed_summary %}
{{ application.parsed_summary }}
{% else %}
<span class="text-gray-400 italic">{% trans "No summary available" %}</span>
{% endif %}
</p> </p>
</div> </div>
<div class="section"> <!-- Work Experience -->
<h2 class="section-title"> <div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
<span>💼</span> Work Experience <h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
<span>💼</span> {% trans "Work Experience" %}
</h2> </h2>
<div class="experience-item">
<div class="job-header"> {% if application.ai_analysis_data.work_experience %}
{% for exp in application.ai_analysis_data.work_experience %}
<div class="mb-6 pb-5 border-b border-gray-200 last:border-0 last:mb-0 last:pb-0">
<div class="flex justify-between items-start mb-2.5">
<div> <div>
<div class="job-title">Head, Recruitment & Training</div> <div class="font-bold text-gray-900 text-lg">{{ exp.title }}</div>
<div class="company">TASNEE - DOWNSTREAM & METALLURGY SBUs</div> <div class="text-gray-600 font-medium">{{ exp.company }}</div>
</div> </div>
<div class="job-period">Oct 2024 - Present</div> <div class="text-gray-500 text-sm whitespace-nowrap">{{ exp.period }}</div>
</div> </div>
<ul class="achievements"> <ul class="list-disc list-inside pl-5 text-gray-600 space-y-1.5">
<li>Led recruitment and training initiatives across downstream and metallurgical divisions</li> {% for responsibility in exp.responsibilities %}
<li>Developed workforce analytics program</li> <li>{{ responsibility }}</li>
{% endfor %}
</ul> </ul>
</div> </div>
{% endfor %}
<div class="experience-item"> {% else %}
<div class="job-header"> <p class="text-gray-400 italic">{% trans "No work experience data available" %}</p>
<div> {% endif %}
<div class="job-title">Human Resources Business Partner</div>
<div class="company">TASNEE METALLURGY SBU</div>
</div>
<div class="job-period">Oct 2015 - Present</div>
</div>
<ul class="achievements">
<li>Implemented HR strategies aligning with business goals</li>
<li>Optimized recruitment processes</li>
<li>Ensured regulatory compliance</li>
</ul>
</div> </div>
<div class="experience-item"> <!-- Education -->
<div class="job-header"> <div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
<div> <h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
<div class="job-title">Specialist, Recruitment</div> <span>🎓</span> {% trans "Education" %}
<div class="company">MARAFIQ</div>
</div>
<div class="job-period">Jul 2011 - Feb 2013</div>
</div>
<ul class="achievements">
<li>Performed recruitment for various roles</li>
<li>Improved selection processes</li>
</ul>
</div>
</div>
<div class="section">
<h2 class="section-title">
<span>🎓</span> Education
</h2> </h2>
<div class="education-item">
<div class="degree">Associate Diploma in People Management Level 5</div> {% if application.ai_analysis_data.education %}
<div class="institution">Chartered Institute of Personnel and Development (CIPD)</div> {% for edu in application.ai_analysis_data.education %}
</div> <div class="mb-4 pb-4 border-b border-gray-200 last:border-0 last:mb-0 last:pb-0">
<div class="education-item"> <div class="font-bold text-gray-900">{{ edu.degree }}</div>
<div class="degree">Bachelor's Degree in Chemical Engineering Technology</div> <div class="text-gray-600">{{ edu.institution }}</div>
<div class="institution">Yanbu Industrial College</div> <div class="text-gray-500 text-sm mt-1">{{ edu.year }}</div>
</div> </div>
{% endfor %}
{% else %}
<p class="text-gray-400 italic">{% trans "No education data available" %}</p>
{% endif %}
</div> </div>
</div> </div>
<div class="right-column"> <!-- Right Column -->
<div class="score-card"> <div class="flex flex-col gap-6">
<div class="match-score">10%</div> <!-- Match Score -->
<div class="score-label">Match Score</div> {% if application.ai_analysis_data %}
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white text-center p-8 rounded-2xl shadow-lg">
<div class="text-5xl font-bold mb-2.5">{{ application.ai_analysis_data.match_score }}%</div>
<div class="text-lg opacity-90">{% trans "Match Score" %}</div>
</div> </div>
{% endif %}
<div class="section"> <!-- Assessment -->
<h2 class="section-title"> {% if application.ai_analysis_data %}
<span>🔍</span> Assessment <div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
<span>🔍</span> {% trans "Assessment" %}
</h2> </h2>
<div class="strengths-weaknesses">
<div class="strength-box"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
<div class="box-title">Strengths</div> <div class="bg-emerald-50 border border-emerald-500 p-4 rounded-xl">
<div class="box-content">Extensive HR leadership and project management experience</div> <div class="font-bold text-emerald-700 mb-2 text-sm flex items-center gap-2">
<span></span> {% trans "Strengths" %}
</div> </div>
<div class="weakness-box"> <div class="text-sm text-gray-700">{{ application.ai_analysis_data.strengths }}</div>
<div class="box-title">Weaknesses</div> </div>
<div class="box-content">Lack of IT infrastructure, cybersecurity, and relevant certifications</div> <div class="bg-red-50 border border-red-500 p-4 rounded-xl">
<div class="font-bold text-red-700 mb-2 text-sm flex items-center gap-2">
<span></span> {% trans "Weaknesses" %}
</div>
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.weaknesses }}</div>
</div> </div>
</div> </div>
<div class="recommendation"> <div class="bg-temple-red/5 border-l-4 border-l-temple-red p-4 rounded-xl">
<div class="recommendation-title">Recommendation</div> <div class="font-bold text-temple-red mb-2 flex items-center gap-2">
<div class="recommendation-text">Candidate does not meet the IT management requirements; not recommended for interview.</div> <span>💡</span> {% trans "Recommendation" %}
</div>
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.recommendation }}</div>
</div> </div>
</div> </div>
{% endif %}
<div class="section"> <!-- Top Keywords -->
<h2 class="section-title"> {% if application.ai_analysis_data %}
<span>💡</span> Top Keywords <div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
<span>💡</span> {% trans "Top Keywords" %}
</h2> </h2>
<div class="skills-container"> <div class="flex flex-wrap gap-2">
<span class="keyword-tag">HR</span> {% for keyword in application.ai_analysis_data.top_3_keywords %}
<span class="keyword-tag">Recruitment</span> <span class="bg-temple-red/10 text-temple-red px-3 py-1.5 rounded-full text-sm font-medium border border-temple-red/20">
<span class="keyword-tag">Training</span> {{ keyword }}
</span>
{% empty %}
<span class="text-gray-400 italic text-sm">{% trans "No keywords available" %}</span>
{% endfor %}
</div>
</div>
{% endif %}
<!-- Skills -->
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
<span>🛠️</span> {% trans "Skills" %}
</h2>
<div class="flex flex-wrap gap-2">
{% if application.ai_analysis_data.skills %}
{% for skill in application.ai_analysis_data.skills %}
<span class="bg-gradient-to-r from-temple-red to-[#7a1a29] text-white px-3 py-1.5 rounded-full text-sm font-medium shadow-sm">
{{ skill }}
</span>
{% endfor %}
{% else %}
<span class="text-gray-400 italic text-sm">{% trans "No skills data available" %}</span>
{% endif %}
</div> </div>
</div> </div>
<div class="section"> <!-- Professional Details -->
<h2 class="section-title"> {% if application.ai_analysis_data %}
<span>🛠️</span> Skills <div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
<span>👤</span> {% trans "Professional Details" %}
</h2> </h2>
<div class="skills-container">
<span class="skill-tag">Workforce Analytics</span>
<span class="skill-tag">Succession Planning</span>
<span class="skill-tag">Organizational Development</span>
<span class="skill-tag">Recruitment & Selection</span>
<span class="skill-tag">Employee Engagement</span>
<span class="skill-tag">Training Needs Analysis</span>
<span class="skill-tag">Performance Management</span>
<span class="skill-tag">Time Management</span>
<span class="skill-tag">Negotiation Skills</span>
<span class="skill-tag">SAP & HRIS Systems</span>
</div>
</div>
<div class="section"> <div class="flex justify-between items-center mb-3">
<h2 class="section-title"> <span class="text-gray-600 text-sm">{% trans "Years of Experience:" %}</span>
<span>🌍</span> Languages <strong class="text-gray-900">{{ application.ai_analysis_data.years_of_experience }}</strong>
</h2>
<div class="language-item">
<span class="language-name">Arabic</span>
<span class="proficiency">Native</span>
</div> </div>
<div class="language-item"> <div class="flex justify-between items-center mb-3">
<span class="language-name">English</span> <span class="text-gray-600 text-sm">{% trans "Recent Job Title:" %}</span>
<span class="proficiency">Fluent</span> <strong class="text-gray-900 text-sm text-right">{{ application.ai_analysis_data.most_recent_job_title }}</strong>
</div> </div>
<div class="language-item"> <div class="flex justify-between items-center mb-3">
<span class="language-name">Japanese</span> <span class="text-gray-600 text-sm">{% trans "Industry Match:" %}</span>
<span class="proficiency">Intermediate</span> <span class="px-2.5 py-0.5 rounded-full text-xs font-bold {% if application.ai_analysis_data.experience_industry_match >= 70 %}bg-emerald-500 text-white{% elif application.ai_analysis_data.experience_industry_match >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
{{ application.ai_analysis_data.experience_industry_match }}%
</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-600 text-sm">{% trans "Soft Skills Score:" %}</span>
<strong class="text-gray-900">{{ application.ai_analysis_data.soft_skills_score }}%</strong>
</div>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</body>
</html>

View File

@ -1,23 +1,26 @@
<div class="card mt-4"> {% load i18n %}
<div class="card-header text-primary-theme"> <div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
<h5 class="card-title mb-0">{% trans "Add Comment" %}</h5> <div class="bg-gray-50 px-4 py-3 border-b border-gray-200">
<h5 class="text-lg font-bold text-temple-red mb-0">{% trans "Add Comment" %}</h5>
</div> </div>
<div class="card-body"> <div class="p-4">
<form method="post" action="{% url 'add_meeting_comment' meeting.slug %}"> <form method="post" action="{% url 'add_meeting_comment' meeting.slug %}">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> <div class="mb-3">
{{ form.content }} {{ form.content }}
{% if form.content.errors %} {% if form.content.errors %}
<div class="text-danger mt-1"> <div class="text-red-600 mt-1 text-sm">
{% for error in form.content.errors %} {% for error in form.content.errors %}
<small>{{ error }}</small> <small>{{ error }}</small>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
<button type="submit" class="btn btn-primary">{% trans "Add Comment" %}</button> <button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2 rounded-lg text-sm transition shadow-sm hover:shadow-md">
{% trans "Add Comment" %}
</button>
{% if 'HX-Request' in request.headers %} {% if 'HX-Request' in request.headers %}
<button type="button" class="btn btn-secondary" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">Cancel</button> <button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2 rounded-lg text-sm transition" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">{% trans "Cancel" %}</button>
{% endif %} {% endif %}
</form> </form>
</div> </div>

View File

@ -1,56 +1,59 @@
{% load i18n %}
<div id="comment-section"> <div id="comment-section">
<div class="card mt-4"> <div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
<div class="card-header text-primary-theme d-flex justify-content-between align-items-center"> <div class="bg-gray-50 px-4 py-3 border-b border-gray-200 flex items-center justify-between">
<h5 class="card-title mb-0">{% trans "Comments" %} ({{ comments.count }})</h5> <h5 class="text-lg font-bold text-temple-red mb-0">{% trans "Comments" %} ({{ comments.count }})</h5>
{% if 'HX-Request' in request.headers %} {% if 'HX-Request' in request.headers %}
<button type="button" class="btn btn-light btn-sm" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section"> <button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-3 py-1.5 rounded-lg text-sm transition" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">
<i class="bi bi-x-lg"></i> {% trans "Close" %} <i data-lucide="x" class="w-4 h-4"></i> {% trans "Close" %}
</button> </button>
{% endif %} {% endif %}
</div> </div>
<div class="card-body"> <div class="p-4">
{% if comments %} {% if comments %}
<div class="row"> <div class="space-y-3">
{% for comment in comments %} {% for comment in comments %}
<div class="col-12 mb-3"> <div class="bg-gray-50 rounded-xl overflow-hidden">
<div class="card"> <div class="px-4 py-3 bg-white border-b border-gray-100 flex items-center justify-between">
<div class="card-header d-flex justify-content-between align-items-start ">
<div> <div>
<strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong> <strong class="text-gray-900">{{ comment.author.get_full_name|default:comment.author.username }}</strong>
{% if comment.author != user %} {% if comment.author != user %}
<span class="badge bg-secondary ms-2">{% trans "Comment" %}</span> <span class="inline-block ml-2 px-2 py-0.5 bg-gray-200 text-gray-600 text-xs rounded-full">{% trans "Comment" %}</span>
{% endif %} {% endif %}
</div> </div>
<small class="text-muted">{{ comment.created_at|date:"M d, Y P" }}</small> <small class="text-gray-500 text-sm">{{ comment.created_at|date:"M d, Y P" }}</small>
</div> </div>
<div class="card-body"> <div class="px-4 py-3">
<p class="card-text">{{ comment.content|safe }}</p> <p class="text-gray-700 mb-0">{{ comment.content|safe }}</p>
</div> </div>
<div class="card-footer"> <div class="px-4 py-2 bg-gray-50 border-t border-gray-100">
{% if comment.author == user or user.is_staff %} {% if comment.author == user or user.is_staff %}
<div class="btn-group btn-group-sm"> <div class="flex items-center gap-2">
<button type="button" class="btn btn-outline-primary" <button type="button" class="inline-flex items-center gap-1 px-3 py-1.5 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white rounded-lg text-sm transition"
hx-get="{% url 'edit_meeting_comment' meeting.slug comment.id %}" hx-get="{% url 'edit_meeting_comment' meeting.slug comment.id %}"
hx-target="#comment-section" hx-target="#comment-section"
title="Edit Comment"> title="Edit Comment">
<i class="bi bi-pencil"></i> <i data-lucide="pencil" class="w-4 h-4"></i>
</button> </button>
<button type="button" class="btn btn-outline-danger" <button type="button" class="inline-flex items-center gap-1 px-3 py-1.5 border border-red-500 text-red-500 hover:bg-red-500 hover:text-white rounded-lg text-sm transition"
hx-get="{% url 'delete_meeting_comment' meeting.slug comment.id %}" hx-get="{% url 'delete_meeting_comment' meeting.slug comment.id %}"
hx-target="#comment-section" hx-target="#comment-section"
title="Delete Comment"> title="Delete Comment">
<i class="bi bi-trash"></i> <i data-lucide="trash-2" class="w-4 h-4"></i>
</button> </button>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}
<p class="text-muted">{% trans "No comments yet. Be the first to comment!" %}</p> <p class="text-gray-500 text-center py-4">{% trans "No comments yet. Be the first to comment!" %}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
<script>
lucide.createIcons();
</script>

View File

@ -1,20 +1,58 @@
<div class="toast-container position-fixed bottom-0 end-0 p-3"> {% load i18n %}
<div id="copyToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header"> <!-- Toast Notification -->
<i class="fas fa-check-circle text-success me-2"></i> <div id="copyToast" class="fixed bottom-4 right-4 z-50 transform translate-y-2 opacity-0 transition-all duration-300 pointer-events-none" role="alert" aria-live="assertive" aria-atomic="true">
<strong class="me-auto">{% trans "Success" %}</strong> <div class="bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden pointer-events-auto min-w-[320px]">
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button> <div class="flex items-center justify-between px-4 py-3 border-b border-gray-100 bg-gray-50">
<div class="flex items-center gap-2">
<i data-lucide="check-circle" class="w-5 h-5 text-green-600"></i>
<strong class="text-gray-900">{% trans "Success" %}</strong>
</div> </div>
<div class="toast-body"> <button type="button" onclick="hideToast()" class="text-gray-400 hover:text-gray-600 transition">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div class="px-4 py-3 text-sm text-gray-700">
{% blocktrans with text=text %}Copied "{{ text }}" to clipboard!{% endblocktrans %} {% blocktrans with text=text %}Copied "{{ text }}" to clipboard!{% endblocktrans %}
</div> </div>
</div> </div>
</div> </div>
<script> <script>
// Show toast notification let toastTimeout;
function showToast() {
const toast = document.getElementById('copyToast');
if (toast) {
// Clear any existing timeout
if (toastTimeout) {
clearTimeout(toastTimeout);
}
// Show toast
toast.classList.remove('translate-y-2', 'opacity-0', 'pointer-events-none');
toast.classList.add('translate-y-0', 'opacity-100');
// Auto-hide after 3 seconds
toastTimeout = setTimeout(() => {
hideToast();
}, 3000);
}
}
function hideToast() {
const toast = document.getElementById('copyToast');
if (toast) {
toast.classList.add('translate-y-2', 'opacity-0', 'pointer-events-none');
toast.classList.remove('translate-y-0', 'opacity-100');
}
}
// Show toast on page load
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const toast = new bootstrap.Toast(document.getElementById('copyToast')); showToast();
toast.show();
}); });
// Initialize icons
lucide.createIcons();
</script> </script>

View File

@ -1,15 +1,18 @@
<div class="card mt-4"> {% load i18n %}
<div class="card-header text-primary-theme"> <div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
<h5 class="card-title mb-0">{% trans "Delete Comment" %}</h5> <div class="bg-gray-50 px-4 py-3 border-b border-gray-200">
<h5 class="text-lg font-bold text-temple-red mb-0">{% trans "Delete Comment" %}</h5>
</div> </div>
<div class="card-body"> <div class="p-4">
<p>{% trans "Are you sure you want to delete this comment by" %} <strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong>?</p> <p class="text-gray-600 mb-2">{% trans "Are you sure you want to delete this comment by" %} <strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong>?</p>
<p><small>{{ comment.created_at|date:"F d, Y P" }}</small></p> <p class="text-sm text-gray-500 mb-4"><small>{{ comment.created_at|date:"F d, Y P" }}</small></p>
<form method="post" action="{{ delete_url }}"> <form method="post" action="{{ delete_url }}">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-danger">{% trans "Yes, Delete" %}</button> <button type="submit" class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white font-medium px-4 py-2 rounded-lg text-sm transition shadow-sm hover:shadow-md">
{% trans "Yes, Delete" %}
</button>
{% if 'HX-Request' in request.headers %} {% if 'HX-Request' in request.headers %}
<button type="button" class="btn btn-secondary" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">{% trans "Cancel" %}</button> <button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2 rounded-lg text-sm transition" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">{% trans "Cancel" %}</button>
{% endif %} {% endif %}
</form> </form>
</div> </div>

View File

@ -1,18 +1,22 @@
{% load i18n %} {% load i18n %}
<!-- Delete Confirmation Modal --> <!-- Delete Confirmation Modal -->
<div class="modal fade" id="meetingModal" tabindex="-1" aria-labelledby="meetingModalLabel" aria-hidden="true"> <div class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4" id="meetingModal" tabindex="-1" aria-labelledby="meetingModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="bg-white rounded-xl shadow-xl max-w-md w-full">
<div class="modal-content"> <div class="flex items-center justify-between p-4 border-b border-gray-200">
<div class="modal-header"> <h5 class="text-lg font-bold text-gray-900" id="meetingModalLabel">
<h5 class="modal-title" id="meetingModalLabel">
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="text-gray-400 hover:text-gray-600 transition" aria-label="Close" onclick="document.getElementById('meetingModal').classList.add('hidden')">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div> </div>
<div id="meetingModalBody" class="modal-body px-4 py-3"> <div id="meetingModalBody" class="p-4">
</div> </div>
</div> </div>
</div> </div>
</div>
<script>
lucide.createIcons();
</script>

View File

@ -4,35 +4,18 @@
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden"> <div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
<div class="flex items-center justify-between p-4 border-b border-gray-200 bg-gray-50"> <div class="flex items-center justify-between p-4 border-b border-gray-200 bg-gray-50">
<h5 class="text-sm font-bold text-kaauh-blue flex items-center gap-2"> <h5 class="text-sm font-bold text-temple-red flex items-center gap-2">
<i data-lucide="folder-open" class="w-5 h-5"></i> <i data-lucide="folder-open" class="w-5 h-5"></i>
{% trans "Documents" %} {% trans "Documents" %}
</h5> </h5>
<button type="button" <button type="button"
class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-4 py-2 rounded-xl text-sm transition shadow-sm hover:shadow-md" class="upload-modal-trigger inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2 rounded-xl text-sm transition shadow-sm hover:shadow-md"
onclick="document.getElementById('documentUploadModal').classList.remove('hidden')"> data-modal="documentUploadModal">
<i data-lucide="upload-cloud" class="w-4 h-4"></i> <i data-lucide="upload-cloud" class="w-4 h-4"></i>
{% trans "Upload Document" %} {% trans "Upload Document" %}
</button> </button>
</div> </div>
<!-- Document Upload Modal -->
<div id="documentUploadModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-2xl shadow-xl max-w-lg w-full">
<div class="flex items-center justify-between p-4 border-b border-gray-200">
<h5 class="text-lg font-bold text-gray-900">{% trans "Upload Document" %}</h5>
<button type="button"
onclick="document.getElementById('documentUploadModal').classList.add('hidden')"
class="text-gray-400 hover:text-gray-600 transition">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div class="p-4">
{% include "forms/document_form.html" with slug=application.slug %}
</div>
</div>
</div>
<!-- Documents List --> <!-- Documents List -->
<div class="p-4" id="document-list-container"> <div class="p-4" id="document-list-container">
{% if documents %} {% if documents %}
@ -41,7 +24,7 @@
<div id="document-{{document.pk}}" class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 p-3 bg-gray-50 rounded-xl hover:bg-gray-100 transition"> <div id="document-{{document.pk}}" class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 p-3 bg-gray-50 rounded-xl hover:bg-gray-100 transition">
<div class="flex items-start gap-3 flex-1"> <div class="flex items-start gap-3 flex-1">
<div class="flex-shrink-0 mt-0.5"> <div class="flex-shrink-0 mt-0.5">
<i data-lucide="file" class="w-8 h-8 text-kaauh-blue"></i> <i data-lucide="file" class="w-8 h-8 text-temple-red"></i>
</div> </div>
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<p class="font-semibold text-gray-900 mb-1">{{ document.get_document_type_display }}</p> <p class="font-semibold text-gray-900 mb-1">{{ document.get_document_type_display }}</p>
@ -58,7 +41,7 @@
<div class="flex items-center gap-2 flex-shrink-0"> <div class="flex items-center gap-2 flex-shrink-0">
<a href="{% url 'document_download' document.id %}" <a href="{% url 'document_download' document.id %}"
class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-white border border-gray-200 text-gray-600 hover:text-kaauh-blue hover:border-kaauh-blue transition" class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-white border border-gray-200 text-gray-600 hover:text-temple-red hover:border-temple-red transition"
title='{% trans "Download" %}'> title='{% trans "Download" %}'>
<i data-lucide="download" class="w-4 h-4"></i> <i data-lucide="download" class="w-4 h-4"></i>
</a> </a>
@ -87,15 +70,19 @@
</div> </div>
</div> </div>
<script> <!-- Document Upload Modal (moved outside the card structure to avoid z-index issues) -->
// Initialize Lucide icons <div id="documentUploadModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-[60] hidden flex items-center justify-center p-4">
lucide.createIcons(); <div class="bg-white rounded-2xl shadow-xl max-w-lg w-full">
<div class="flex items-center justify-between p-4 border-b border-gray-200">
// Close modal on outside click <h5 class="text-lg font-bold text-gray-900">{% trans "Upload Document" %}</h5>
document.addEventListener('click', function(e) { <button type="button"
const modal = document.getElementById('documentUploadModal'); class="modal-close-btn text-gray-400 hover:text-gray-600 transition"
if (e.target === modal) { data-modal="documentUploadModal">
modal.classList.add('hidden'); <i data-lucide="x" class="w-5 h-5"></i>
} </button>
}); </div>
</script> <div class="p-4">
{% include "forms/document_form.html" with slug=application.slug %}
</div>
</div>
</div>

View File

@ -4,345 +4,191 @@
{% block title %}{% trans "Audit Dashboard" %}{% endblock %} {% block title %}{% trans "Audit Dashboard" %}{% endblock %}
{% block customCSS %}
<style>
/* ---------------------------------------------------- */
/* 1. Theme Variables (Teal Focus) */
/* ---------------------------------------------------- */
:root {
--color-primary: #007a88; /* Main Teal */
--color-primary-dark: #004d55; /* Dark Teal for Headings, Login Status, and Strong Text */
/* Standard Dark Text (for max visibility) */
/* Muted text */
--color-text-on-dark: #f0f0f0; /* Light text for badges (guaranteed contrast) */
/* Adjusted Status Colors for contrast */
--color-success: #157347;
--color-danger: #bb2d3b;
--color-warning: #ffc107;
--color-info: #0dcaf0;
--color-light: #f8f9fa;
}
/* ---------------------------------------------------- */
/* 2. Layout, Header, and Summary */
/* ---------------------------------------------------- */
.container-fluid {
background-color: var(--color-background-light);
}
.audit-card {
background-color: #ffffff;
border-radius: 0.75rem;
border: 1px solid #e9ecef;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
min-height: 60vh;
}
.dashboard-header {
color: var(--color-primary-dark);
border-bottom: 1px solid #e9ecef;
padding-bottom: 1rem;
}
.summary-alert {
border-color: var(--color-primary) !important;
background-color: var(--color-primary-light) !important;
}
.summary-alert h6, .summary-alert strong {
color: var(--color-primary-dark) !important;
}
/* ---------------------------------------------------- */
/* 3. Tabs Styling */
/* ---------------------------------------------------- */
.nav-tabs {
border-bottom: 2px solid #e9ecef;
background-color: #ffffff;
padding-top: 1rem;
border-radius: 0.75rem 0.75rem 0 0;
}
.nav-link-es {
color: var(--color-text-secondary);
border: none;
}
.nav-link-es.active {
color: var(--color-primary) !important;
font-weight: 600;
border-bottom: 3px solid var(--color-primary) !important;
}
.nav-link:hover:not(.active) {
color: var(--color-primary);
}
/* ---------------------------------------------------- */
/* 4. Table and Text Contrast */
/* ---------------------------------------------------- */
.table th {
color: var(--color-text-secondary);
}
.table td {
color: var(--color-text-dark);
}
pre {
background-color: var(--color-light) !important;
color: var(--color-text-dark) !important;
}
code {
color: var(--color-text-dark) !important;
}
/* ---------------------------------------------------- */
/* 5. badges VISIBILITY FIXES (Guaranteed Colors) */
/* ---------------------------------------------------- */
.badges {
font-weight: 600;
line-height: 1.4;
padding: 0.4em 0.6em;
min-width: 65px;
text-align: center;
text-transform: uppercase;
/* Ensure z-index doesn't cause issues if elements overlap */
position: relative;
}
/* Dark badgess (CRUD Create/Delete & Login/Logout Type) - Use light text */
.badges-crud-create {
background-color: var(--color-success) !important;
color: var(--color-text-on-dark) !important;
}
.badges-crud-delete {
background-color: var(--color-danger) !important;
color: var(--color-text-on-dark) !important;
}
.badges-login-status {
background-color: var(--color-primary-dark) !important;
color: var(--color-text-on-dark) !important;
}
/* Light badgess (CRUD Update & Request Method) - Use dark text */
.badges-crud-update {
background-color: var(--color-warning) !important;
color: var(--color-text-dark) !important;
}
.badges-request-method {
background-color: var(--color-info) !important;
color: var(--color-text-dark) !important;
}
/* Pagination - Fully Teal Themed */
.pagination .page-item.active .page-link {
background-color: var(--color-primary) !important;
border-color: var(--color-primary) !important;
color: var(--color-text-on-dark) !important;
}
.pagination .page-link {
color: var(--color-primary) !important; /* FIX: Added !important here */
border: 1px solid #dee2e6;
}
.pagination .page-item.disabled .page-link {
color: var(--color-text-secondary) !important;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<!-- Breadcrumb -->
<div class="container-fluid pt-5 pb-5 px-lg-5" style="background-color: var(--color-background-light);"> <nav aria-label="breadcrumb" class="mb-4">
<nav aria-label="breadcrumb"> <ol class="flex items-center gap-2 text-sm">
<ol class="breadcrumb"> <li>
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li> <a href="{% url 'settings' %}" class="text-gray-600 hover:text-temple-red transition">{% trans "Settings" %}</a>
<li class="breadcrumb-item active" aria-current="page" style=" </li>
color: #F43B5E; /* Rosy Accent Color */ <li class="text-gray-400">/</li>
font-weight: 600; <li class="font-semibold text-temple-red">{% trans "System Activity" %}</li>
">{% trans "System Activity" %}</li>
</ol> </ol>
</nav> </nav>
<h1 class="h3 fw-bold dashboard-header mb-4 px-3">
<i class="fas fa-shield-alt me-2" style="color: var(--color-primary);"></i>{% trans "System Activity Logs" %}
</h1>
<div class="alert summary-alert border-start border-5 p-3 mb-5 mx-3" role="alert"> <!-- Header -->
<h6 class="mb-1">{% trans "Viewing Logs" %}: <strong>{{ tab_title }}</strong></h6> <div class="mb-4">
<p class="mb-0 small"> <h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
{% trans "Displaying" %}: **{{ logs.start_index }}-{{ logs.end_index }}** {% trans "of" %} <i data-lucide="shield" class="w-6 h-6 text-temple-red"></i>
**{{ total_count }}** {% trans "total records." %} {% trans "System Activity Logs" %}
</h1>
</div>
<!-- Summary Alert -->
<div class="bg-temple-red/10 border-l-4 border-temple-red p-4 mb-6 rounded-r-lg">
<div class="text-sm font-semibold text-gray-800 mb-1">
{% trans "Viewing Logs" %}: <strong>{{ tab_title }}</strong>
</div>
<p class="text-sm text-gray-700">
{% trans "Displaying" %}: <strong>{{ logs.start_index }}-{{ logs.end_index }}</strong> {% trans "of" %}
<strong>{{ total_count }}</strong> {% trans "total records." %}
</p> </p>
</div> </div>
<div class="audit-card mx-3"> <!-- Audit Card -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 min-h-[60vh]">
<ul class="nav nav-tabs px-3" id="auditTabs" role="tablist"> <!-- Tabs -->
<li class="nav-item" role="presentation"> <div class="flex border-b border-gray-200 px-3 pt-4 rounded-t-xl">
<a class="nav-link nav-link-es {% if active_tab == 'crud' %}active{% endif %}" <a class="px-4 py-2 text-sm font-medium transition border-b-3 {% if active_tab == 'crud' %}text-temple-red border-temple-red{% else %}text-gray-600 border-transparent hover:text-temple-red hover:border-gray-300{% endif %}"
id="crud-tab" href="?tab=crud" aria-controls="crud"> href="?tab=crud">
<i class="fas fa-database me-2"></i>{% trans "Model Changes (CRUD)" %} <i data-lucide="database" class="w-4 h-4 inline mr-2"></i>{% trans "Model Changes (CRUD)" %}
</a> </a>
</li> <a class="px-4 py-2 text-sm font-medium transition border-b-3 {% if active_tab == 'login' %}text-temple-red border-temple-red{% else %}text-gray-600 border-transparent hover:text-temple-red hover:border-gray-300{% endif %}"
<li class="nav-item" role="presentation"> href="?tab=login">
<a class="nav-link nav-link-es {% if active_tab == 'login' %}active{% endif %}" <i data-lucide="user-lock" class="w-4 h-4 inline mr-2"></i>{% trans "User Authentication" %}
id="login-tab" href="?tab=login" aria-controls="login">
<i class="fas fa-user-lock me-2"></i>{% trans "User Authentication" %}
</a> </a>
</li> <a class="px-4 py-2 text-sm font-medium transition border-b-3 {% if active_tab == 'request' %}text-temple-red border-temple-red{% else %}text-gray-600 border-transparent hover:text-temple-red hover:border-gray-300{% endif %}"
<li class="nav-item" role="presentation"> href="?tab=request">
<a class="nav-link nav-link-es {% if active_tab == 'request' %}active{% endif %}" <i data-lucide="globe" class="w-4 h-4 inline mr-2"></i>{% trans "HTTP Requests" %}
id="request-tab" href="?tab=request" aria-controls="request">
<i class="fas fa-globe me-2"></i>{% trans "HTTP Requests" %}
</a> </a>
</li> </div>
</ul>
<div class="tab-content p-4" id="auditTabsContent"> <!-- Tab Content -->
<div class="p-4">
<div class="tab-pane fade show active" role="tabpanel"> <div class="overflow-x-auto">
<table class="w-full text-sm">
<div class="table-responsive">
<table class="table table-striped table-hover small">
<thead> <thead>
{% if active_tab == 'crud' %} {% if active_tab == 'crud' %}
<tr> <tr class="bg-gray-50">
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
<th scope="col" style="width: 15%;">{% trans "User" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
<th scope="col" style="width: 10%;">{% trans "Action" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Action" %}</th>
<th scope="col" style="width: 20%;">{% trans "Model" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Model" %}</th>
<th scope="col" style="width: 10%;">{% trans "Object PK" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Object PK" %}</th>
<th scope="col" style="width: 30%;">{% trans "Changes" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Changes" %}</th>
</tr> </tr>
{% elif active_tab == 'login' %} {% elif active_tab == 'login' %}
<tr> <tr class="bg-gray-50">
<th scope="col" style="width: 20%;">{% trans "Date/Time" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
<th scope="col" style="width: 25%;">{% trans "User" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
<th scope="col" style="width: 15%;">{% trans "Type" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Type" %}</th>
<th scope="col" style="width: 10%;">{% trans "Status" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Status" %}</th>
<th scope="col" style="width: 30%;">{% trans "IP Address" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "IP Address" %}</th>
</tr> </tr>
{% elif active_tab == 'request' %} {% elif active_tab == 'request' %}
<tr> <tr class="bg-gray-50">
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
<th scope="col" style="width: 15%;">{% trans "User" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
<th scope="col" style="width: 10%;">{% trans "Method" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Method" %}</th>
<th scope="col" style="width: 45%;">{% trans "Path" %}</th> <th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Path" %}</th>
</tr> </tr>
{% endif %} {% endif %}
</thead> </thead>
<tbody> <tbody class="divide-y divide-gray-100">
{% for log in logs.object_list %} {% for log in logs.object_list %}
{% if active_tab == 'crud' %} {% if active_tab == 'crud' %}
<tr> <tr class="hover:bg-gray-50">
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td> <td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
<td>{{ log.user.email|default:"N/A" }}</td> <td class="px-4 py-3 text-gray-900">{{ log.user.email|default:"N/A" }}</td>
<td> <td class="px-4 py-3">
<span class="badge rounded-pill {% if log.event_type == 1 %}
{% if log.event_type == 1 %}badges-crud-create <span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i data-lucide="plus" class="w-3 h-3"></i>{% trans "CREATE" %}</span>
{% elif log.event_type == 2 %}badges-crud-update {% elif log.event_type == 2 %}
{% else %}badges-crud-delete{% endif %}"> <span class="inline-flex items-center gap-1 px-2.5 py-1 bg-yellow-100 text-yellow-800 rounded-full text-xs font-medium"><i data-lucide="edit" class="w-3 h-3"></i>{% trans "UPDATE" %}</span>
{% if log.event_type == 1 %}<i class="fas fa-plus-circle me-1"></i>{% trans "CREATE" %} {% else %}
{% elif log.event_type == 2 %}<i class="fas fa-edit me-1"></i>{% trans "UPDATE" %} <span class="inline-flex items-center gap-1 px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium"><i data-lucide="trash-2" class="w-3 h-3"></i>{% trans "DELETE" %}</span>
{% else %}<i class="fas fa-trash-alt me-1"></i>{% trans "DELETE" %}{% endif %} {% endif %}
</span>
</td> </td>
<td><code style="color: var(--color-text-dark) !important;">{{ log.content_type.app_label }}.{{ log.content_type.model }}</code></td> <td class="px-4 py-3"><code class="bg-gray-100 px-2 py-1 rounded text-xs">{{ log.content_type.app_label }}.{{ log.content_type.model }}</code></td>
<td>{{ log.object_id }}</td> <td class="px-4 py-3 text-gray-900">{{ log.object_id }}</td>
<td> <td class="px-4 py-3">
<pre class="p-2 m-0" style="font-size: 0.65rem; max-height: 80px; overflow-y: auto;">{{ log.changed_fields }}</pre> <pre class="bg-gray-100 p-2 rounded text-xs overflow-x-auto max-h-20 overflow-y-auto">{{ log.changed_fields }}</pre>
</td> </td>
</tr> </tr>
{% elif active_tab == 'login' %} {% elif active_tab == 'login' %}
<tr> <tr class="hover:bg-gray-50">
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td> <td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
<td> <td class="px-4 py-3 text-gray-900">
{% with user_obj=log.user %} {% with user_obj=log.user %}
{% if user_obj %} {% if user_obj %}
{{ user_obj.get_full_name|default:user_obj.username }} {{ user_obj.get_full_name|default:user_obj.username }}
{% else %} {% else %}
<span class="text-danger fw-bold">{{ log.username|default:"N/A" }}</span> <span class="text-red-600 font-semibold">{{ log.username|default:"N/A" }}</span>
{% endif %} {% endif %}
{% endwith %} {% endwith %}
</td> </td>
<td> <td class="px-4 py-3">
<span class="badge rounded-pill badges-login-status"> <span class="inline-block px-2.5 py-1 bg-gray-700 text-white rounded-full text-xs font-medium">
{% if log.login_type == 0 %}{% trans "Login" %} {% if log.login_type == 0 %}{% trans "Login" %}
{% elif log.login_type == 1 %}{% trans "Logout" %} {% elif log.login_type == 1 %}{% trans "Logout" %}
{% else %}{% trans "Failed Login" %}{% endif %} {% else %}{% trans "Failed Login" %}{% endif %}
</span> </span>
</td> </td>
<td> <td class="px-4 py-3">
{% if log.login_type == 2 %} {% if log.login_type == 2 %}
<i class="fas fa-times-circle me-1" style="color: var(--color-danger);"></i>{% trans "Failed" %} <span class="inline-flex items-center gap-1 text-red-600"><i data-lucide="x-circle" class="w-4 h-4"></i>{% trans "Failed" %}</span>
{% else %} {% else %}
<i class="fas fa-check-circle me-1" style="color: var(--color-success);"></i>{% trans "Success" %} <span class="inline-flex items-center gap-1 text-green-600"><i data-lucide="check-circle" class="w-4 h-4"></i>{% trans "Success" %}</span>
{% endif %} {% endif %}
</td> </td>
<td>{{ log.remote_ip|default:"Unknown" }}</td> <td class="px-4 py-3 text-gray-900">{{ log.remote_ip|default:"Unknown" }}</td>
</tr> </tr>
{% elif active_tab == 'request' %} {% elif active_tab == 'request' %}
<tr> <tr class="hover:bg-gray-50">
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td> <td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
<td>{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td> <td class="px-4 py-3 text-gray-900">{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td>
<td> <td class="px-4 py-3">
<span class="badge rounded-pill badges-request-method">{{ log.method }}</span> <span class="inline-block px-2.5 py-1 bg-gray-700 text-white rounded-full text-xs font-medium">{{ log.method }}</span>
</td> </td>
<td><code class="text-break small" style="color: var(--color-text-dark) !important;">{{ log.url}}</code></td> <td class="px-4 py-3"><code class="bg-gray-100 px-2 py-1 rounded text-xs break-all">{{ log.url}}</code></td>
</tr> </tr>
{% endif %} {% endif %}
{% empty %} {% empty %}
<tr><td colspan="6" class="text-center text-muted py-5"> <tr>
<i class="fas fa-info-circle me-2"></i>{% trans "No logs found for this section or the database is empty." %} <td colspan="6" class="px-4 py-12 text-center text-gray-500">
</td></tr> <i data-lucide="info" class="w-5 h-5 inline mr-2"></i>{% trans "No logs found for this section or database is empty." %}
</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
<!-- Pagination -->
{% if logs.has_other_pages %} {% if logs.has_other_pages %}
<nav aria-label="Audit Log Pagination" class="pt-3"> <div class="flex justify-end items-center gap-2 pt-4">
<ul class="pagination justify-content-end"> <a href="?tab={{ active_tab }}{% if logs.has_previous %}&page={{ logs.previous_page_number }}{% endif %}"
class="px-3 py-2 text-sm font-medium rounded-lg {% if logs.has_previous %}bg-white border border-gray-300 text-temple-red hover:bg-gray-50{% else %}bg-gray-100 text-gray-400 cursor-not-allowed{% endif %} transition">
<li class="page-item {% if not logs.has_previous %}disabled{% endif %}"> <i data-lucide="chevron-left" class="w-4 h-4 inline"></i>
<a class="page-link"
href="?tab={{ active_tab }}{% if logs.has_previous %}&page={{ logs.previous_page_number }}{% endif %}"
aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
</a> </a>
</li>
{% for i in logs.paginator.page_range %} {% for i in logs.paginator.page_range %}
{% comment %} Limiting pages displayed around the current page {% endcomment %}
{% if i > logs.number|add:'-3' and i < logs.number|add:'3' %} {% if i > logs.number|add:'-3' and i < logs.number|add:'3' %}
<li class="page-item {% if logs.number == i %}active{% endif %}"> <a href="?tab={{ active_tab }}&page={{ i }}"
<a class="page-link" class="px-3 py-2 text-sm font-medium rounded-lg {% if logs.number == i %}bg-temple-red text-white{% else %}bg-white border border-gray-300 text-temple-red hover:bg-gray-50{% endif %} transition">
href="?tab={{ active_tab }}&page={{ i }}">
{{ i }} {{ i }}
</a> </a>
</li>
{% elif i == logs.number|add:'-3' or i == logs.number|add:'3' %} {% elif i == logs.number|add:'-3' or i == logs.number|add:'3' %}
<li class="page-item disabled"><span class="page-link">...</span></li> <span class="px-3 py-2 text-sm text-gray-400">...</span>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<li class="page-item {% if not logs.has_next %}disabled{% endif %}"> <a href="?tab={{ active_tab }}{% if logs.has_next %}&page={{ logs.next_page_number }}{% endif %}"
<a class="page-link" class="px-3 py-2 text-sm font-medium rounded-lg {% if logs.has_next %}bg-white border border-gray-300 text-temple-red hover:bg-gray-50{% else %}bg-gray-100 text-gray-400 cursor-not-allowed{% endif %} transition">
href="?tab={{ active_tab }}{% if logs.has_next %}&page={{ logs.next_page_number }}{% endif %}" <i data-lucide="chevron-right" class="w-4 h-4 inline"></i>
aria-label="Next">
<span aria-hidden="true">&raquo;</span>
</a> </a>
</li> </div>
</ul>
</nav>
{% endif %} {% endif %}
</div> </div>
</div>
</div>
</div> </div>
<script>
lucide.createIcons();
</script>
{% endblock %} {% endblock %}

View File

@ -1,23 +1,26 @@
<div class="card mt-4"> {% load i18n %}
<div class="card-header text-primary-theme"> <div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
<h5 class="card-title mb-0">{% trans "Edit Comment" %}</h5> <div class="bg-gray-50 px-4 py-3 border-b border-gray-200">
<h5 class="text-lg font-bold text-temple-red mb-0">{% trans "Edit Comment" %}</h5>
</div> </div>
<div class="card-body"> <div class="p-4">
<form method="post" action="{% url 'edit_meeting_comment' meeting.slug comment.id %}"> <form method="post" action="{% url 'edit_meeting_comment' meeting.slug comment.id %}">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> <div class="mb-3">
{{ form.content }} {{ form.content }}
{% if form.content.errors %} {% if form.content.errors %}
<div class="text-danger mt-1"> <div class="text-red-600 mt-1 text-sm">
{% for error in form.content.errors %} {% for error in form.content.errors %}
<small>{{ error }}</small> <small>{{ error }}</small>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
<button type="submit" class="btn bg-primary btn-sm">{% trans "Update Comment" %}</button> <button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2 rounded-lg text-sm transition shadow-sm hover:shadow-md">
{% trans "Update Comment" %}
</button>
{% if 'HX-Request' in request.headers %} {% if 'HX-Request' in request.headers %}
<button type="button" class="btn btn-secondary btn-sm" hx-get="{% url 'meeting_details' meeting.slug %}" hx-target="#comment-section">{% trans "Cancel" %}</button> <button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2 rounded-lg text-sm transition" hx-get="{% url 'meeting_details' meeting.slug %}" hx-target="#comment-section">{% trans "Cancel" %}</button>
{% endif %} {% endif %}
</form> </form>
</div> </div>

View File

@ -1,59 +1,60 @@
{% load i18n %} {% load i18n %}
{{ form.media }} {{ form.media }}
<div class="row">
<div class="container-fluid"> <div class="w-full max-w-2xl mx-auto">
<!-- Messages -->
<div class="col-12">
{% if messages %} {% if messages %}
<ul class="messages"> <div class="space-y-2 mb-4" role="alert" aria-live="polite">
{% for message in messages %} {% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}> <div class="alert {% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-800{% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-800{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-800{% else %}bg-blue-50 border-blue-200 text-blue-800{% endif %} border rounded-lg px-4 py-3 flex items-start justify-between gap-2">
{{ message }} <span class="flex-1 text-sm">{{ message }}</span>
</li> <button type="button"
class="text-gray-400 hover:text-gray-600 p-1 shrink-0 touch-target"
onclick="this.parentElement.remove()"
aria-label="{% trans 'Dismiss' %}">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div>
{% endfor %} {% endfor %}
</ul> </div>
{% endif %} {% endif %}
<div class="card">
<div class="card-body"> <!-- Form Card -->
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="p-6">
<form hx-boost="true" method="post" id="email-compose-form" action="{% url 'compose_application_email' job.slug %}" <form hx-boost="true" method="post" id="email-compose-form" action="{% url 'compose_application_email' job.slug %}"
hx-include="#application-form" hx-include="#application-form"
hx-push-url="false" hx-push-url="false"
hx-swap="outerHTML" hx-swap="outerHTML"
hx-on::after-request="new bootstrap.Modal('#emailModal')).hide()"> hx-on::after-request="closeEmailModal()"
class="space-y-6">
{% csrf_token %} {% csrf_token %}
<!-- Recipients Field --> <!-- Recipients Field -->
<!-- Recipients Field --> <div>
<div class="mb-3"> <label class="block text-sm font-semibold text-gray-700 mb-2">
<label class="form-label fw-bold">
{% trans "To" %} {% trans "To" %}
</label> </label>
<div class="border rounded p-3 bg-light" style="max-height: 200px; overflow-y: auto;"> <div class="border-2 border-gray-300 rounded-lg p-4 bg-gray-50 max-h-48 overflow-y-auto">
<!-- Hidden inputs for all recipients -->
{# --- 1. DATA LAYER: Render Hidden Inputs for ALL recipients --- #}
{# This ensures the backend receives every selected user, not just the visible one #}
{% for choice in form.to %} {% for choice in form.to %}
<input type="hidden" name="{{ form.to.name }}" value="{{ choice.data.value }}"> <input type="hidden" name="{{ form.to.name }}" value="{{ choice.data.value }}">
{% endfor %} {% endfor %}
{# --- 2. VISUAL LAYER: Show only the first one --- #} <!-- Show first recipient only -->
{# We make it disabled so the user knows they can't deselect it here #}
{% for choice in form.to|slice:":1" %} {% for choice in form.to|slice:":1" %}
<div class="form-check mb-2"> <div class="flex items-center gap-2 mb-2">
<input class="form-check-input" type="checkbox" checked disabled> <div class="w-4 h-4 rounded border-2 border-temple-red bg-temple-red flex items-center justify-center">
<label class="form-check-label"> <i data-lucide="check" class="w-3 h-3 text-white"></i>
{{ choice.choice_label }} </div>
</label> <span class="text-sm text-gray-700">{{ choice.choice_label }}</span>
</div> </div>
{% endfor %} {% endfor %}
{# --- 3. SUMMARY: Show count of hidden recipients --- #} <!-- Summary of remaining recipients -->
{% if form.to|length > 1 %} {% if form.to|length > 1 %}
<div class="text-muted small mt-2"> <div class="flex items-center gap-2 text-gray-600 text-sm mt-2 pt-2 border-t border-gray-200">
<i class="fas fa-info-circle me-1"></i> <i data-lucide="info" class="w-4 h-4 text-temple-red"></i>
{# Use simple math to show remaining count #}
{% with remaining=form.to|length|add:"-1" %} {% with remaining=form.to|length|add:"-1" %}
{% blocktrans count total=remaining %} {% blocktrans count total=remaining %}
And {{ total }} other recipient And {{ total }} other recipient
@ -66,63 +67,77 @@
</div> </div>
{% if form.to.errors %} {% if form.to.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-2">
{% for error in form.to.errors %} {% for error in form.to.errors %}
<span>{{ error }}</span> <span class="block">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
<!-- Subject Field --> <!-- Subject Field -->
<div class="mb-3"> <div>
<label for="{{ form.subject.id_for_label }}" class="form-label fw-bold"> <label for="{{ form.subject.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "Subject" %} {% trans "Subject" %}
</label> </label>
{{ form.subject }} <div class="relative">
<input type="text"
name="{{ form.subject.name }}"
id="{{ form.subject.id_for_label }}"
class="w-full px-4 py-2.5 rounded-lg border-2 border-gray-300 focus:border-temple-red focus:ring-2 focus:ring-temple-red/20 focus:outline-none transition text-gray-900 placeholder-gray-400 text-sm"
placeholder="{% trans 'Enter email subject...' %}"
value="{{ form.subject.value|default:'' }}"
required>
{% if form.subject.errors %} {% if form.subject.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-2">
{% for error in form.subject.errors %} {% for error in form.subject.errors %}
<span>{{ error }}</span> <span class="block">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
<!-- Message Field --> <!-- Message Field -->
<div class="mb-3"> <div>
<label for="{{ form.message.id_for_label }}" class="form-label fw-bold"> <label for="{{ form.message.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "Message" %} {% trans "Message" %}
</label> </label>
{{ form.message }} <div class="relative">
<textarea
name="{{ form.message.name }}"
id="{{ form.message.id_for_label }}"
rows="8"
class="w-full px-4 py-2.5 rounded-lg border-2 border-gray-300 focus:border-temple-red focus:ring-2 focus:ring-temple-red/20 focus:outline-none transition text-gray-900 placeholder-gray-400 text-sm resize-y"
placeholder="{% trans 'Write your message here...' %}"
required>{{ form.message.value|default:'' }}</textarea>
{% if form.message.errors %} {% if form.message.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-2">
{% for error in form.message.errors %} {% for error in form.message.errors %}
<span>{{ error }}</span> <span class="block">{{ error }}</span>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
<!-- Form Actions --> <!-- Form Actions -->
<div class="d-flex justify-content-between align-items-center"> <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 pt-4 border-t border-gray-200">
<div class="text-muted small"> <div class="flex items-center gap-2 text-gray-600 text-sm">
<i class="fas fa-info-circle me-1"></i> <i data-lucide="info" class="w-4 h-4 text-temple-red"></i>
{% trans "Email will be sent to all selected recipients" %} {% trans "Email will be sent to all selected recipients" %}
</div> </div>
<div> <div class="flex items-center gap-3 w-full sm:w-auto">
<button type="button" <button type="button"
class="btn btn-secondary me-2" class="flex-1 sm:flex-none flex items-center justify-center gap-2 px-6 py-2.5 rounded-lg border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 hover:border-gray-400 transition font-medium"
data-bs-dismiss="modal"> onclick="closeEmailModal()">
<i class="fas fa-times me-1"></i> <i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel" %} {% trans "Cancel" %}
</button> </button>
<button type="submit" <button type="submit"
class="btn btn-primary" class="flex-1 sm:flex-none flex items-center justify-center gap-2 px-6 py-2.5 rounded-lg bg-temple-red text-white hover:bg-red-700 transition font-medium shadow-sm"
id="send-email-btn"> id="send-email-btn">
<i class="fas fa-paper-plane me-1"></i> <i data-lucide="send" class="w-4 h-4"></i>
{% trans "Send Email" %} {% trans "Send Email" %}
</button> </button>
</div> </div>
@ -130,88 +145,37 @@
</form> </form>
</div> </div>
</div> </div>
</div>
</div>
<!-- Loading Overlay --> <!-- Loading Overlay -->
<div id="email-loading-overlay" class="d-none"> <div id="email-loading-overlay" class="hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center">
<div class="d-flex justify-content-center align-items-center" style="min-height: 200px;"> <div class="bg-white rounded-xl shadow-lg p-8 max-w-sm w-full mx-4">
<div class="text-center"> <div class="flex flex-col items-center gap-4">
<div class="spinner-border text-primary" role="status"> <div class="w-12 h-12 rounded-full border-4 border-temple-red border-t-transparent animate-spin" role="status">
<span class="visually-hidden">{% trans "Loading..." %}</span> <span class="sr-only">{% trans "Loading..." %}</span>
</div> </div>
<div class="mt-2"> <div class="text-center">
{% trans "Sending email..." %} <p class="text-lg font-semibold text-gray-800">{% trans "Sending email..." %}</p>
<p class="text-sm text-gray-600 mt-1">{% trans "Please wait..." %}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Success/Error Messages Container --> <!-- Success/Error Messages Container -->
<div id="email-messages-container"></div> <div id="email-messages-container" class="fixed bottom-4 right-4 z-50 space-y-2 max-w-md"></div>
</div> </div>
<style>
{{ form.media.css }}
.card {
border: none;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border-radius: 8px;
}
.card-header {
border-radius: 8px 8px 0 0 !important;
border-bottom: none;
}
.form-control:focus {
border-color: #00636e;
box-shadow: 0 0 0 0.2rem rgba(0,99,110,0.25);
}
.btn-primary {
background-color: #00636e;
border-color: #00636e;
}
.btn-primary:hover {
background-color: #004a53;
border-color: #004a53;
}
.form-check-input:checked {
background-color: #00636e;
border-color: #00636e;
}
.border {
border-color: #dee2e6 !important;
}
.bg-light {
background-color: #f8f9fa !important;
}
.text-danger {
color: #dc3545 !important;
}
.spinner-border {
width: 3rem;
height: 3rem;
}
</style>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('email-compose-form1'); const form = document.getElementById('email-compose-form');
const sendBtn = document.getElementById('send-email-btn1'); const sendBtn = document.getElementById('send-email-btn');
const loadingOverlay = document.getElementById('email-loading-overlay'); const loadingOverlay = document.getElementById('email-loading-overlay');
const messagesContainer = document.getElementById('email-messages-container'); const messagesContainer = document.getElementById('email-messages-container');
// Initialize Lucide icons
lucide.createIcons();
if (form) { if (form) {
form.addEventListener('submit', function(e) { form.addEventListener('submit', function(e) {
e.preventDefault(); e.preventDefault();
@ -219,11 +183,12 @@ document.addEventListener('DOMContentLoaded', function() {
// Show loading state // Show loading state
if (sendBtn) { if (sendBtn) {
sendBtn.disabled = true; sendBtn.disabled = true;
sendBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i> {% trans "Sending..." %}'; sendBtn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> {% trans "Sending..." %}';
lucide.createIcons();
} }
if (loadingOverlay) { if (loadingOverlay) {
loadingOverlay.classList.remove('d-none'); loadingOverlay.classList.remove('hidden');
} }
// Clear previous messages // Clear previous messages
@ -247,29 +212,24 @@ document.addEventListener('DOMContentLoaded', function() {
// Hide loading state // Hide loading state
if (sendBtn) { if (sendBtn) {
sendBtn.disabled = false; sendBtn.disabled = false;
sendBtn.innerHTML = '<i class="fas fa-paper-plane me-1"></i> {% trans "Send Email" %}'; sendBtn.innerHTML = '<i data-lucide="send" class="w-4 h-4"></i> {% trans "Send Email" %}';
lucide.createIcons();
} }
if (loadingOverlay) { if (loadingOverlay) {
loadingOverlay.classList.add('d-none'); loadingOverlay.classList.add('hidden');
} }
// Show result message // Show result message
if (data.success) { if (data.success || data.status === 'queued') {
showMessage(data.message || 'Email sent successfully!', 'success'); showMessage(data.message || 'Email enqueued successfully!', 'success');
// Close modal after a short delay // Close modal after a short delay
setTimeout(() => { setTimeout(() => {
const modal = form.closest('.modal'); closeEmailModal();
if (modal) {
const bootstrapModal = bootstrap.Modal.getInstance(modal);
if (bootstrapModal) {
bootstrapModal.hide();
}
}
}, 1500); }, 1500);
} else { } else {
showMessage(data.error || 'Failed to send email. Please try again.', 'danger'); showMessage(data.error || data.message || 'Failed to send email. Please try again.', 'danger');
} }
}) })
.catch(error => { .catch(error => {
@ -278,11 +238,12 @@ document.addEventListener('DOMContentLoaded', function() {
// Hide loading state // Hide loading state
if (sendBtn) { if (sendBtn) {
sendBtn.disabled = false; sendBtn.disabled = false;
sendBtn.innerHTML = '<i class="fas fa-paper-plane me-1"></i> {% trans "Send Email" %}'; sendBtn.innerHTML = '<i data-lucide="send" class="w-4 h-4"></i> {% trans "Send Email" %}';
lucide.createIcons();
} }
if (loadingOverlay) { if (loadingOverlay) {
loadingOverlay.classList.add('d-none'); loadingOverlay.classList.add('hidden');
} }
showMessage('An error occurred while sending the email. Please try again.', 'danger'); showMessage('An error occurred while sending the email. Please try again.', 'danger');
@ -293,116 +254,62 @@ document.addEventListener('DOMContentLoaded', function() {
function showMessage(message, type) { function showMessage(message, type) {
if (!messagesContainer) return; if (!messagesContainer) return;
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger'; const bgColor = type === 'success' ? 'bg-green-50 border-green-200 text-green-800' : 'bg-red-50 border-red-200 text-red-800';
const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-triangle'; const icon = type === 'success' ? 'check-circle' : 'alert-triangle';
const messageHtml = ` const messageDiv = document.createElement('div');
<div class="alert ${alertClass} alert-dismissible fade show" role="alert"> messageDiv.className = `alert ${bgColor} border rounded-lg px-4 py-3 flex items-start gap-2 shadow-lg transform transition-all duration-300 translate-x-full`;
<i class="fas ${icon} me-2"></i> messageDiv.innerHTML = `
${message} <i data-lucide="${icon}" class="w-5 h-5 shrink-0 mt-0.5"></i>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <span class="flex-1 text-sm">${message}</span>
</div> <button type="button" class="text-gray-400 hover:text-gray-600 p-1 shrink-0" onclick="this.parentElement.remove()">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
`; `;
messagesContainer.innerHTML = messageHtml; messagesContainer.appendChild(messageDiv);
lucide.createIcons();
// Animate in
requestAnimationFrame(() => {
messageDiv.classList.remove('translate-x-full');
});
// Auto-hide success messages after 5 seconds // Auto-hide success messages after 5 seconds
if (type === 'success') { if (type === 'success') {
setTimeout(() => { setTimeout(() => {
const alert = messagesContainer.querySelector('.alert'); messageDiv.style.opacity = '0';
if (alert) { messageDiv.style.transform = 'translateX(100%)';
const bsAlert = new bootstrap.Alert(alert); setTimeout(() => messageDiv.remove(), 300);
bsAlert.close();
}
}, 5000); }, 5000);
} }
} }
// Form validation
function validateForm() {
let isValid = true;
const subject = form.querySelector('#{{ form.subject.id_for_label }}');
const message = form.querySelector('#{{ form.message.id_for_label }}');
const recipients = form.querySelectorAll('input[name="{{ form.recipients.name }}"]:checked');
// Clear previous validation states
form.querySelectorAll('.is-invalid').forEach(field => {
field.classList.remove('is-invalid');
});
form.querySelectorAll('.invalid-feedback').forEach(feedback => {
feedback.remove();
});
// Validate subject
if (!subject || !subject.value.trim()) {
showFieldError(subject, 'Subject is required');
isValid = false;
}
// Validate message
if (!message || !message.value.trim()) {
showFieldError(message, 'Message is required');
isValid = false;
}
// Validate recipients
if (recipients.length === 0) {
const recipientsContainer = form.querySelector('.border.rounded.p-3.bg-light');
if (recipientsContainer) {
recipientsContainer.classList.add('border-danger');
showFieldError(recipientsContainer, 'Please select at least one recipient');
}
isValid = false;
}
return isValid;
}
function showFieldError(field, message) {
if (!field) return;
field.classList.add('is-invalid');
const feedback = document.createElement('div');
feedback.className = 'invalid-feedback';
feedback.textContent = message;
if (field.classList.contains('border')) {
// For container elements (like recipients)
field.parentNode.appendChild(feedback);
} else {
// For form fields
field.parentNode.appendChild(feedback);
}
}
// Character counter for message field // Character counter for message field
function setupCharacterCounter() { function setupCharacterCounter() {
const messageField = form.querySelector('#{{ form.message.id_for_label }}'); const messageField = form.querySelector('#{{ form.message.id_for_label }}');
if (!messageField) return; if (!messageField) return;
const counter = document.createElement('div'); const counter = document.createElement('div');
counter.className = 'text-muted small mt-1'; counter.className = 'text-gray-500 text-sm mt-2 text-right';
counter.id = 'message-counter'; counter.id = 'message-counter';
messageField.parentNode.appendChild(counter); messageField.parentNode.appendChild(counter);
function updateCounter() { function updateCounter() {
const length = messageField.value.length; const length = messageField.value.length;
const maxLength = 5000; // Adjust as needed const maxLength = 5000;
counter.textContent = `${length} / ${maxLength} characters`; counter.textContent = `${length} / ${maxLength} characters`;
if (length > maxLength * 0.9) { if (length > maxLength * 0.9) {
counter.classList.add('text-warning'); counter.className = 'text-yellow-600 text-sm mt-2 text-right';
counter.classList.remove('text-muted');
} else { } else {
counter.classList.remove('text-warning'); counter.className = 'text-gray-500 text-sm mt-2 text-right';
counter.classList.add('text-muted');
} }
} }
messageField.addEventListener('input', updateCounter); messageField.addEventListener('input', updateCounter);
updateCounter(); // Initial count updateCounter();
} }
// Auto-save functionality // Auto-save functionality
@ -417,17 +324,14 @@ document.addEventListener('DOMContentLoaded', function() {
const draftData = { const draftData = {
subject: subject.value, subject: subject.value,
message: message.value, message: message.value,
recipients: Array.from(form.querySelectorAll('input[name="{{ form.recipients.name }}"]:checked')).map(cb => cb.value), timestamp: new Date().toISOString()
include_application_info: form.querySelector('#{{ form.include_application_info.id_for_label }}').checked,
include_meeting_details: form.querySelector('#{{ form.include_meeting_details.id_for_label }}').checked
}; };
localStorage.setItem('email_draft_' + window.location.pathname, JSON.stringify(draftData)); localStorage.setItem('email_draft_' + window.location.pathname, JSON.stringify(draftData));
} }
function autoSave() { function autoSave() {
clearTimeout(autoSaveTimer); clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(saveDraft, 2000); // Save after 2 seconds of inactivity autoSaveTimer = setTimeout(saveDraft, 2000);
} }
[subject, message].forEach(field => { [subject, message].forEach(field => {
@ -450,21 +354,6 @@ document.addEventListener('DOMContentLoaded', function() {
if (subject && draft.subject) subject.value = draft.subject; if (subject && draft.subject) subject.value = draft.subject;
if (message && draft.message) message.value = draft.message; if (message && draft.message) message.value = draft.message;
// Restore recipients
if (draft.recipients) {
form.querySelectorAll('input[name="{{ form.recipients.name }}"]').forEach(cb => {
cb.checked = draft.recipients.includes(cb.value);
});
}
// Restore checkboxes
if (draft.include_application_info) {
form.querySelector('#{{ form.include_application_info.id_for_label }}').checked = draft.include_application_info;
}
if (draft.include_meeting_details) {
form.querySelector('#{{ form.include_meeting_details.id_for_label }}').checked = draft.include_meeting_details;
}
// Show draft restored notification // Show draft restored notification
showMessage('Draft restored from local storage', 'success'); showMessage('Draft restored from local storage', 'success');
} catch (e) { } catch (e) {
@ -484,16 +373,7 @@ document.addEventListener('DOMContentLoaded', function() {
setTimeout(loadDraft, 100); setTimeout(loadDraft, 100);
// Clear draft on successful submission // Clear draft on successful submission
const originalSubmitHandler = form.onsubmit; form.addEventListener('submit', function() {
form.addEventListener('submit', function(e) {
const isValid = validateForm();
if (!isValid) {
e.preventDefault();
e.stopPropagation();
return false;
}
// Clear draft on successful submission
setTimeout(clearDraft, 2000); setTimeout(clearDraft, 2000);
}); });
@ -509,16 +389,19 @@ document.addEventListener('DOMContentLoaded', function() {
// Escape to cancel/close modal // Escape to cancel/close modal
if (e.key === 'Escape') { if (e.key === 'Escape') {
const modal = form.closest('.modal'); closeEmailModal();
if (modal) {
const bootstrapModal = bootstrap.Modal.getInstance(modal);
if (bootstrapModal) {
bootstrapModal.hide();
}
}
} }
}); });
console.log('Email compose form initialized'); console.log('Email compose form initialized');
}); });
// Global function to close email modal
function closeEmailModal() {
const modal = document.querySelector('[id^="emailModal"]');
if (modal) {
modal.classList.add('hidden');
document.body.style.overflow = '';
}
}
</script> </script>

View File

@ -1,18 +1,18 @@
{% load i18n %} {% load i18n %}
<li> <li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %} <form action="{% url 'set_language' %}" method="post" class="inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ next_url }}"> <input name="next" type="hidden" value="{{ next_url }}">
<button name="language" value="en" class="dropdown-item {% if LANGUAGE_CODE == 'en' %}active bg-light-subtle{% endif %}" type="submit"> <button name="language" value="en" class="w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100 hover:text-temple-red {% if LANGUAGE_CODE == 'en' %}bg-temple-red text-white hover:bg-temple-dark{% endif %} transition" type="submit">
<span class="me-2">🇺🇸</span> English <span class="mr-2">🇺🇸</span> English
</button> </button>
</form> </form>
</li> </li>
<li> <li>
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %} <form action="{% url 'set_language' %}" method="post" class="inline">{% csrf_token %}
<input name="next" type="hidden" value="{{ next_url }}"> <input name="next" type="hidden" value="{{ next_url }}">
<button name="language" value="ar" class="dropdown-item {% if LANGUAGE_CODE == 'ar' %}active bg-light-subtle{% endif %}" type="submit"> <button name="language" value="ar" class="w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100 hover:text-temple-red {% if LANGUAGE_CODE == 'ar' %}bg-temple-red text-white hover:bg-temple-dark{% endif %} transition" type="submit">
<span class="me-2">🇸🇦</span> العربية <span class="mr-2">🇸🇦</span> العربية
</button> </button>
</form> </form>
</li> </li>

View File

@ -1,44 +1,45 @@
{% load i18n %}
<!-- This snippet is loaded by HTMX into #meetingModalBody --> <!-- This snippet is loaded by HTMX into #meetingModalBody -->
<form id="meetingForm" method="post" action="{{ action_url }}?_target=modal" data-bs-theme="light"> <form id="meetingForm" method="post" action="{{ action_url }}?_target=modal">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="candidate_pk" value="{{ candidate.pk }}"> <input type="hidden" name="candidate_pk" value="{{ candidate.pk }}">
{% if scheduled_interview %} {% if scheduled_interview %}
<input type="hidden" name="interview_pk" value="{{ scheduled_interview.pk }}"> <input type="hidden" name="interview_pk" value="{{ scheduled_interview.pk }}">
{% endif %} {% endif %}
<div class="mb-3"> <div class="mb-4">
<label for="id_topic" class="form-label">{% trans "Topic" %}</label> <label for="id_topic" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Topic" %}</label>
<input type="text" class="form-control" id="id_topic" name="topic" value="{{ initial_data.topic|default:'' }}" required> <input type="text" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_topic" name="topic" value="{{ initial_data.topic|default:'' }}" required>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label for="id_start_time" class="form-label">{% trans "Start Time and Date" %}</label> <label for="id_start_time" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time and Date" %}</label>
<input type="datetime-local" class="form-control" id="id_start_time" name="start_time" value="{{ initial_data.start_time|default:'' }}" required> <input type="datetime-local" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_start_time" name="start_time" value="{{ initial_data.start_time|default:'' }}" required>
</div> </div>
<div class="mb-3"> <div class="mb-4">
<label for="id_duration" class="form-label">{% trans "Duration (minutes)" %}</label> <label for="id_duration" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Duration (minutes)" %}</label>
<input type="number" class="form-control" id="id_duration" name="duration" value="{{ initial_data.duration|default:60 }}" min="15" step="15" required> <input type="number" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_duration" name="duration" value="{{ initial_data.duration|default:60 }}" min="15" step="15" required>
</div> </div>
<div id="meetingDetails" class="alert alert-info" style="display: none;"> <div id="meetingDetails" class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4" style="display: none;">
<strong>{% trans "Meeting Details (will appear after scheduling):" %}</strong> <strong class="block text-blue-900 mb-2">{% trans "Meeting Details (will appear after scheduling):" %}</strong>
<p><strong>{% trans "Join URL:" %}</strong> <a id="joinUrlDisplay" href="#" target="_blank"></a></p> <p class="mb-1"><strong>{% trans "Join URL:" %}</strong> <a id="joinUrlDisplay" href="#" target="_blank" class="text-temple-red hover:underline"></a></p>
<p><strong>{% trans "Meeting ID:" %}</strong> <span id="meetingIdDisplay"></span></p> <p class="mb-0"><strong>{% trans "Meeting ID:" %}</strong> <span id="meetingIdDisplay"></span></p>
</div> </div>
<div id="successMessage" class="alert alert-success" style="display: none;"> <div id="successMessage" class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4" style="display: none;">
<span id="successText"></span> <span id="successText" class="block text-green-900"></span>
<small><a id="joinLinkSuccess" href="#" target="_blank" style="color: inherit; text-decoration: underline;">{% trans "Click here to join meeting" %}</a></small> <small><a id="joinLinkSuccess" href="#" target="_blank" class="text-green-700 hover:underline">{% trans "Click here to join meeting" %}</a></small>
</div> </div>
<div id="errorMessage" class="alert alert-danger" style="display: none;"> <div id="errorMessage" class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4" style="display: none;">
<span id="errorText"></span> <span id="errorText" class="block text-red-900"></span>
</div> </div>
<div class="d-flex justify-content-end gap-2 mt-4"> <div class="flex items-center justify-end gap-3 mt-4">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-dismiss="modal">{% trans "Cancel" %}</button> <button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2.5 rounded-lg text-sm transition" onclick="document.getElementById('meetingModal').classList.add('hidden')">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-primary" id="scheduleBtn"> <button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2.5 rounded-lg text-sm transition shadow-sm hover:shadow-md" id="scheduleBtn">
{% if scheduled_interview %}{% trans "Reschedule Meeting" %}{% else %}{% trans "Schedule Meeting" %}{% endif %} {% if scheduled_interview %}{% trans "Reschedule Meeting" %}{% else %}{% trans "Schedule Meeting" %}{% endif %}
</button> </button>
</div> </div>
@ -57,20 +58,8 @@ document.addEventListener('DOMContentLoaded', function() {
const joinLinkSuccess = document.getElementById('joinLinkSuccess'); const joinLinkSuccess = document.getElementById('joinLinkSuccess');
const errorMessageDiv = document.getElementById('errorMessage'); const errorMessageDiv = document.getElementById('errorMessage');
const errorText = document.getElementById('errorText'); const errorText = document.getElementById('errorText');
const modalElement = document.getElementById('meetingModal'); // This should be on the parent page
const modalTitle = document.querySelector('#meetingModal .modal-title'); // Parent page element
// Update modal title based on data attributes from the triggering button (if available)
// This is a fallback, ideally the parent page JS updates the title before fetching.
// Or, the view context could set a variable for the title.
// For simplicity, we'll assume parent page JS or rely on initial context.
const modalTitleText = modalTitle.getAttribute('data-current-title') || "{% trans 'Schedule Interview' %}";
if (modalTitle) {
modalTitle.textContent = modalTitleText;
}
const submitBtnText = scheduleBtn.getAttribute('data-current-submit-text') || "{% trans 'Schedule Meeting' %}";
scheduleBtn.textContent = submitBtnText;
const submitBtnText = scheduleBtn.textContent || "{% trans 'Schedule Meeting' %}";
form.addEventListener('submit', function(event) { form.addEventListener('submit', function(event) {
event.preventDefault(); event.preventDefault();
@ -80,12 +69,11 @@ document.addEventListener('DOMContentLoaded', function() {
errorMessageDiv.style.display = 'none'; errorMessageDiv.style.display = 'none';
scheduleBtn.disabled = true; scheduleBtn.disabled = true;
scheduleBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> {% trans "Processing..." %}'; scheduleBtn.innerHTML = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" 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 "Processing..." %}';
const formData = new FormData(form); const formData = new FormData(form);
// Check if the target is modal, if so, we might want to close it on success
const isModalTarget = new URLSearchParams(window.location.search).get('_target') === 'modal'; const isModalTarget = new URLSearchParams(window.location.search).get('_target') === 'modal';
const url = form.action.replace(/\?.*$/, ""); // Remove any existing query params like _target const url = form.action.replace(/\?.*$/, "");
fetch(url, { fetch(url, {
method: 'POST', method: 'POST',
@ -95,10 +83,10 @@ document.addEventListener('DOMContentLoaded', function() {
'Accept': 'application/json', 'Accept': 'application/json',
}, },
}) })
.then(response => response.json()) // Always expect JSON for HTMX success/error .then(response => response.json())
.then(data => { .then(data => {
scheduleBtn.disabled = false; scheduleBtn.disabled = false;
scheduleBtn.innerHTML = submitBtnText; // Reset to original text scheduleBtn.textContent = submitBtnText;
if (data.success) { if (data.success) {
successText.textContent = data.message; successText.textContent = data.message;
@ -107,24 +95,21 @@ document.addEventListener('DOMContentLoaded', function() {
joinUrlDisplay.textContent = data.join_url; joinUrlDisplay.textContent = data.join_url;
joinUrlDisplay.href = data.join_url; joinUrlDisplay.href = data.join_url;
joinLinkSuccess.href = data.join_url; joinLinkSuccess.href = data.join_url;
meetingDetailsDiv.style.display = 'block'; // Keep meeting details shown meetingDetailsDiv.style.display = 'block';
} }
if (data.meeting_id) { if (data.meeting_id) {
meetingIdDisplay.textContent = data.meeting_id; meetingIdDisplay.textContent = data.meeting_id;
} }
if (isModalTarget && modalElement) { if (isModalTarget) {
const bsModal = bootstrap.Modal.getInstance(modalElement); const modalElement = document.getElementById('meetingModal');
if (bsModal) bsModal.hide(); if (modalElement) {
modalElement.classList.add('hidden');
}
} }
// Optionally, trigger an event on the parent page to update its list
if (window.parent && window.parent.dispatchEvent) { if (window.parent && window.parent.dispatchEvent) {
window.parent.dispatchEvent(new CustomEvent('meetingUpdated', { detail: data })); window.parent.dispatchEvent(new CustomEvent('meetingUpdated', { detail: data }));
} else {
// Fallback: reload the page if it's not in an iframe or parent dispatch is not available
// window.location.reload();
} }
} else { } else {
errorText.textContent = data.error || '{% trans "An unknown error occurred." %}'; errorText.textContent = data.error || '{% trans "An unknown error occurred." %}';
errorMessageDiv.style.display = 'block'; errorMessageDiv.style.display = 'block';
@ -133,13 +118,12 @@ document.addEventListener('DOMContentLoaded', function() {
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
scheduleBtn.disabled = false; scheduleBtn.disabled = false;
scheduleBtn.innerHTML = submitBtnText; scheduleBtn.textContent = submitBtnText;
errorText.textContent = '{% trans "An error occurred while processing your request." %}'; errorText.textContent = '{% trans "An error occurred while processing your request." %}';
errorMessageDiv.style.display = 'block'; errorMessageDiv.style.display = 'block';
}); });
}); });
// Repopulate if initial_data was passed (for rescheduling)
{% if initial_data %} {% if initial_data %}
document.getElementById('id_topic').value = '{{ initial_data.topic }}'; document.getElementById('id_topic').value = '{{ initial_data.topic }}';
document.getElementById('id_start_time').value = '{{ initial_data.start_time }}'; document.getElementById('id_start_time').value = '{{ initial_data.start_time }}';

View File

@ -1,63 +1,60 @@
<div class="container mt-4"> {% load i18n %}
<h1>Schedule Interviews for {{ job.title }}</h1> <div class="container mx-auto mt-8 px-4">
<h1 class="text-2xl font-bold text-gray-900 mb-6">{% trans "Schedule Interviews for" %} {{ job.title }}</h1>
<div class="card mt-4"> <div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
<div class="card-body"> <div class="p-6">
<form method="post" id="schedule-form"> <form method="post" id="schedule-form">
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="col-md-6"> <!-- Left Column - Candidates -->
<h5>{% trans "Select Candidates" %}</h5> <div>
<div class="form-group"> <h5 class="text-lg font-semibold text-gray-900 mb-4">{% trans "Select Candidates" %}</h5>
<div class="space-y-2">
{{ form.candidates }} {{ form.candidates }}
</div> </div>
</div> </div>
<div class="col-md-6"> <!-- Right Column - Schedule Details -->
<h5>{% trans "Schedule Details" %}</h5> <div>
<h5 class="text-lg font-semibold text-gray-900 mb-4">{% trans "Schedule Details" %}</h5>
<div class="form-group mb-3"> <div class="space-y-4">
<label for="{{ form.start_date.id_for_label }}">{% trans "Start Date" %}</label> <div>
<label for="{{ form.start_date.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Date" %}</label>
{{ form.start_date }} {{ form.start_date }}
</div> </div>
<div class="form-group mb-3"> <div>
<label for="{{ form.end_date.id_for_label }}">{% trans "End Date" %}</label> <label for="{{ form.end_date.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Date" %}</label>
{{ form.end_date }} {{ form.end_date }}
</div> </div>
<div class="form-group mb-3"> <div>
<label>{% trans "Working Days" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2">{% trans "Working Days" %}</label>
{{ form.working_days }} {{ form.working_days }}
</div> </div>
<div class="row"> <div class="grid grid-cols-2 gap-4">
<div class="col-md-6"> <div>
<div class="form-group mb-3"> <label for="{{ form.start_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time" %}</label>
<label for="{{ form.start_time.id_for_label }}">{% trans "Start Time" %}</label>
{{ form.start_time }} {{ form.start_time }}
</div> </div>
</div>
<div class="col-md-6"> <div>
<div class="form-group mb-3"> <label for="{{ form.end_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
<label for="{{ form.end_time.id_for_label }}">{% trans "End Time" %}</label>
{{ form.end_time }} {{ form.end_time }}
</div> </div>
</div> </div>
</div>
<div class="row"> <div class="grid grid-cols-2 gap-4">
<div class="col-md-6"> <div>
<div class="form-group mb-3"> <label for="{{ form.interview_duration.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Interview Duration (minutes)" %}</label>
<label for="{{ form.interview_duration.id_for_label }}">{% trans "Interview Duration (minutes)" %}</label>
{{ form.interview_duration }} {{ form.interview_duration }}
</div> </div>
</div>
<div class="col-md-6"> <div>
<div class="form-group mb-3"> <label for="{{ form.buffer_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Buffer Time (minutes)" %}</label>
<label for="{{ form.buffer_time.id_for_label }}">{% trans "Buffer Time (minutes)" %}</label>
{{ form.buffer_time }} {{ form.buffer_time }}
</div> </div>
</div> </div>
@ -65,36 +62,43 @@
</div> </div>
</div> </div>
<div class="row mt-4"> <!-- Break Times -->
<div class="col-12"> <div class="mt-8">
<h5>Break Times</h5> <h5 class="text-lg font-semibold text-gray-900 mb-4">{% trans "Break Times" %}</h5>
<div id="break-times-container"> <div id="break-times-container" class="space-y-4">
{{ break_formset.management_form }} {{ break_formset.management_form }}
{% for form in break_formset %} {% for form in break_formset %}
<div class="break-time-form row mb-2"> <div class="break-time-form grid grid-cols-12 gap-4 items-start">
<div class="col-md-5"> <div class="col-span-5">
<label>{% trans "Start Time" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time" %}</label>
{{ form.start_time }} {{ form.start_time }}
</div> </div>
<div class="col-md-5"> <div class="col-span-5">
<label>{% trans "End Time" %}</label> <label class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
{{ form.end_time }} {{ form.end_time }}
</div> </div>
<div class="col-md-2"> <div class="col-span-2 flex items-end">
<label>&nbsp;</label><br>
{{ form.DELETE }} {{ form.DELETE }}
<button type="button" class="btn btn-danger btn-sm remove-break">{% trans "Remove" %}</button> <button type="button" class="remove-break w-full bg-red-500 hover:bg-red-600 text-white text-sm font-medium px-4 py-2 rounded-lg transition shadow-sm hover:shadow-md">
<i data-lucide="trash-2" class="w-4 h-4 inline mr-1"></i>{% trans "Remove" %}
</button>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<button type="button" id="add-break" class="btn btn-secondary btn-sm mt-2">{% trans "Add Break" %}</button> <button type="button" id="add-break" class="mt-4 bg-gray-200 hover:bg-gray-300 text-gray-700 text-sm font-medium px-4 py-2 rounded-lg transition">
</div> <i data-lucide="plus" class="w-4 h-4 inline mr-1"></i>{% trans "Add Break" %}
</button>
</div> </div>
<div class="mt-4"> <!-- Action Buttons -->
<button type="submit" class="btn btn-primary">{% trans "Preview Schedule" %}</button> <div class="mt-8 flex gap-3">
<a href="{% url 'job_detail' slug=job.slug %}" class="btn btn-secondary">{% trans "Cancel" %}</a> <button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-6 py-2.5 rounded-lg text-sm transition shadow-sm hover:shadow-md">
<i data-lucide="eye" class="w-4 h-4"></i>{% trans "Preview Schedule" %}
</button>
<a href="{% url 'job_detail' slug=job.slug %}" class="inline-flex items-center gap-2 bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium px-6 py-2.5 rounded-lg text-sm transition">
<i data-lucide="x" class="w-4 h-4"></i>{% trans "Cancel" %}
</a>
</div> </div>
</form> </form>
</div> </div>
@ -110,19 +114,20 @@ document.addEventListener('DOMContentLoaded', function() {
addBreakBtn.addEventListener('click', function() { addBreakBtn.addEventListener('click', function() {
const formCount = parseInt(totalFormsInput.value); const formCount = parseInt(totalFormsInput.value);
const newFormHtml = ` const newFormHtml = `
<div class="break-time-form row mb-2"> <div class="break-time-form grid grid-cols-12 gap-4 items-start">
<div class="col-md-5"> <div class="col-span-5">
<label>Start Time</label> <label class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time" %}</label>
<input type="time" name="breaks-${formCount}-start_time" class="form-control" id="id_breaks-${formCount}-start_time"> <input type="time" name="breaks-${formCount}-start_time" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_breaks-${formCount}-start_time">
</div> </div>
<div class="col-md-5"> <div class="col-span-5">
<label>End Time</label> <label class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
<input type="time" name="breaks-${formCount}-end_time" class="form-control" id="id_breaks-${formCount}-end_time"> <input type="time" name="breaks-${formCount}-end_time" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_breaks-${formCount}-end_time">
</div> </div>
<div class="col-md-2"> <div class="col-span-2 flex items-end">
<label>&nbsp;</label><br>
<input type="checkbox" name="breaks-${formCount}-DELETE" id="id_breaks-${formCount}-DELETE" style="display:none;"> <input type="checkbox" name="breaks-${formCount}-DELETE" id="id_breaks-${formCount}-DELETE" style="display:none;">
<button type="button" class="btn btn-danger btn-sm remove-break">Remove</button> <button type="button" class="remove-break w-full bg-red-500 hover:bg-red-600 text-white text-sm font-medium px-4 py-2 rounded-lg transition shadow-sm hover:shadow-md">
<i data-lucide="trash-2" class="w-4 h-4 inline mr-1"></i>{% trans "Remove" %}
</button>
</div> </div>
</div> </div>
`; `;
@ -133,16 +138,20 @@ document.addEventListener('DOMContentLoaded', function() {
breakTimesContainer.appendChild(newForm); breakTimesContainer.appendChild(newForm);
totalFormsInput.value = formCount + 1; totalFormsInput.value = formCount + 1;
lucide.createIcons();
}); });
// Handle remove button clicks // Handle remove button clicks
breakTimesContainer.addEventListener('click', function(e) { breakTimesContainer.addEventListener('click', function(e) {
if (e.target.classList.contains('remove-break')) { if (e.target.closest('.remove-break')) {
const form = e.target.closest('.break-time-form'); const form = e.target.closest('.break-time-form');
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]'); const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
deleteCheckbox.checked = true; deleteCheckbox.checked = true;
form.style.display = 'none'; form.style.display = 'none';
} }
}); });
// Initialize icons
lucide.createIcons();
}); });
</script> </script>

View File

@ -1,5 +1,5 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n crispy_forms_tags %} {% load static i18n %}
{% block title %}{{ schedule.application.name }} - {% trans "Interview Details" %} - ATS{% endblock %} {% block title %}{{ schedule.application.name }} - {% trans "Interview Details" %} - ATS{% endblock %}
@ -354,14 +354,29 @@
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_email_form.subject.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_email_form.subject.id_for_label }}">
{% trans "Subject" %} {% trans "Subject" %}
</label> </label>
{{ interview_email_form.subject }} <input type="text" name="subject" id="{{ interview_email_form.subject.id_for_label }}"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 placeholder:text-gray-400"
placeholder="{% trans 'Enter email subject...' %}"
value="{% if interview_email_form.subject.value %}{{ interview_email_form.subject.value }}{% endif %}">
{% if interview_email_form.subject.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ interview_email_form.subject.errors }}
</div>
{% endif %}
</div> </div>
<div> <div>
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_email_form.message.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_email_form.message.id_for_label }}">
{% trans "Message" %} {% trans "Message" %}
</label> </label>
{{ interview_email_form.message }} <textarea name="message" id="{{ interview_email_form.message.id_for_label }}" rows="5"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[150px] placeholder:text-gray-400"
placeholder="{% trans 'Enter your message...' %}"></textarea>
{% if interview_email_form.message.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ interview_email_form.message.errors }}
</div>
{% endif %}
</div> </div>
<div class="flex gap-3 pt-2"> <div class="flex gap-3 pt-2">
@ -407,21 +422,40 @@
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.new_date.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.new_date.id_for_label }}">
{% trans "New Date" %} {% trans "New Date" %}
</label> </label>
{{ reschedule_form.new_date }} <input type="date" name="new_date" id="{{ reschedule_form.new_date.id_for_label }}"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200">
{% if reschedule_form.new_date.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ reschedule_form.new_date.errors }}
</div>
{% endif %}
</div> </div>
<div> <div>
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.new_time.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.new_time.id_for_label }}">
{% trans "New Time" %} {% trans "New Time" %}
</label> </label>
{{ reschedule_form.new_time }} <input type="time" name="new_time" id="{{ reschedule_form.new_time.id_for_label }}"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200">
{% if reschedule_form.new_time.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ reschedule_form.new_time.errors }}
</div>
{% endif %}
</div> </div>
<div> <div>
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.reason.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.reason.id_for_label }}">
{% trans "Reason for Rescheduling" %} {% trans "Reason for Rescheduling" %}
</label> </label>
{{ reschedule_form.reason }} <textarea name="reason" id="{{ reschedule_form.reason.id_for_label }}" rows="3"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
placeholder="{% trans 'Enter the reason for rescheduling...' %}"></textarea>
{% if reschedule_form.reason.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ reschedule_form.reason.errors }}
</div>
{% endif %}
</div> </div>
<div class="flex gap-3 pt-2"> <div class="flex gap-3 pt-2">
@ -467,7 +501,14 @@
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ cancel_form.cancellation_reason.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ cancel_form.cancellation_reason.id_for_label }}">
{% trans "Reason for Cancellation" %} {% trans "Reason for Cancellation" %}
</label> </label>
{{ cancel_form.cancellation_reason }} <textarea name="cancellation_reason" id="{{ cancel_form.cancellation_reason.id_for_label }}" rows="3"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
placeholder="{% trans 'Enter the reason for cancellation...' %}"></textarea>
{% if cancel_form.cancellation_reason.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ cancel_form.cancellation_reason.errors }}
</div>
{% endif %}
</div> </div>
<div class="bg-yellow-50 border-l-4 border-yellow-400 rounded-lg p-4"> <div class="bg-yellow-50 border-l-4 border-yellow-400 rounded-lg p-4">
@ -520,14 +561,32 @@
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.interview_result.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.interview_result.id_for_label }}">
{% trans "Interview Result" %} {% trans "Interview Result" %}
</label> </label>
{{ interview_result_form.interview_result }} <select name="interview_result" id="{{ interview_result_form.interview_result.id_for_label }}"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base bg-white focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 cursor-pointer">
<option value="">-- {% trans "Select Result" %} --</option>
<option value="passed">{% trans "Passed" %}</option>
<option value="failed">{% trans "Failed" %}</option>
<option value="pending">{% trans "Pending" %}</option>
</select>
{% if interview_result_form.interview_result.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ interview_result_form.interview_result.errors }}
</div>
{% endif %}
</div> </div>
<div> <div>
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.result_comments.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.result_comments.id_for_label }}">
{% trans "Comments" %} {% trans "Comments" %}
</label> </label>
{{ interview_result_form.result_comments }} <textarea name="result_comments" id="{{ interview_result_form.result_comments.id_for_label }}" rows="3"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
placeholder="{% trans 'Enter any comments...' %}"></textarea>
{% if interview_result_form.result_comments.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ interview_result_form.result_comments.errors }}
</div>
{% endif %}
</div> </div>
<div class="flex gap-3 pt-2"> <div class="flex gap-3 pt-2">
@ -569,18 +628,68 @@
<form method="post" action="{% url 'update_interview_status' schedule.slug %}" class="space-y-4 sm:space-y-5"> <form method="post" action="{% url 'update_interview_status' schedule.slug %}" class="space-y-4 sm:space-y-5">
{% csrf_token %} {% csrf_token %}
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.interview_result.id_for_label }}">
{% trans "Interview Result" %}
</label>
<select name="interview_result" id="{{ interview_result_form.interview_result.id_for_label }}"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base bg-white focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 cursor-pointer">
<option value="">-- {% trans "Select Result" %} --</option>
<option value="passed">{% trans "Passed" %}</option>
<option value="failed">{% trans "Failed" %}</option>
<option value="pending">{% trans "Pending" %}</option>
</select>
{% if interview_result_form.interview_result.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ interview_result_form.interview_result.errors }}
</div>
{% endif %}
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.result_comments.id_for_label }}">
{% trans "Comments" %}
</label>
<textarea name="result_comments" id="{{ interview_result_form.result_comments.id_for_label }}" rows="3"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
placeholder="{% trans 'Enter any comments...' %}"></textarea>
{% if interview_result_form.result_comments.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ interview_result_form.result_comments.errors }}
</div>
{% endif %}
</div>
<div> <div>
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status.id_for_label }}">
{% trans "Interview Status" %} {% trans "Interview Status" %}
</label> </label>
{{ interview_status_form.status }} <select name="status" id="{{ interview_status_form.status.id_for_label }}"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base bg-white focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 cursor-pointer">
<option value="">-- {% trans "Select Status" %} --</option>
<option value="scheduled">{% trans "Scheduled" %}</option>
<option value="confirmed">{% trans "Confirmed" %}</option>
<option value="completed">{% trans "Completed" %}</option>
<option value="cancelled">{% trans "Cancelled" %}</option>
</select>
{% if interview_status_form.status.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ interview_status_form.status.errors }}
</div>
{% endif %}
</div> </div>
<div> <div>
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status_notes.id_for_label }}"> <label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status_notes.id_for_label }}">
{% trans "Notes" %} {% trans "Notes" %}
</label> </label>
{{ interview_status_form.status_notes }} <textarea name="status_notes" id="{{ interview_status_form.status_notes.id_for_label }}" rows="3"
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
placeholder="{% trans 'Enter any notes...' %}"></textarea>
{% if interview_status_form.status_notes.errors %}
<div class="mt-1 text-red-600 text-xs">
{{ interview_status_form.status_notes.errors }}
</div>
{% endif %}
</div> </div>
<div class="flex gap-3 pt-2"> <div class="flex gap-3 pt-2">
@ -617,24 +726,39 @@ document.addEventListener('DOMContentLoaded', function() {
// Style form fields function // Style form fields function
function styleFormFields() { function styleFormFields() {
// Style text inputs // Style text inputs (including those wrapped by crispy forms)
document.querySelectorAll('input[type="text"], input[type="email"], input[type="number"], input[type="date"], input[type="time"], input[type="url"], input[type="tel"]').forEach(input => { const inputs = document.querySelectorAll('input[type="text"], input[type="email"], input[type="number"], input[type="date"], input[type="time"], input[type="url"], input[type="tel"]');
inputs.forEach(input => {
if (!input.classList.contains('styled')) { if (!input.classList.contains('styled')) {
input.className = 'w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 placeholder:text-gray-400 styled'; // Remove any existing conflicting classes
input.className = input.className.replace(/form-control/g, 'styled');
input.className = input.className.replace(/textinput/g, '');
// Add base input styling
input.classList.add('w-full', 'px-4', 'py-3', 'rounded-xl', 'border-2', 'border-gray-200', 'text-gray-700', 'text-sm', 'sm:text-base', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition-all', 'duration-200', 'placeholder:text-gray-400', 'styled');
} }
}); });
// Style select dropdowns // Style select dropdowns
document.querySelectorAll('select').forEach(select => { const selects = document.querySelectorAll('select');
selects.forEach(select => {
if (!select.classList.contains('styled')) { if (!select.classList.contains('styled')) {
select.className = 'w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base bg-white focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 cursor-pointer styled'; // Remove any existing conflicting classes
select.className = select.className.replace(/form-control/g, 'styled');
select.className = select.className.replace(/select/g, '');
// Add base select styling
select.classList.add('w-full', 'px-4', 'py-3', 'rounded-xl', 'border-2', 'border-gray-200', 'text-gray-700', 'text-sm', 'sm:text-base', 'bg-white', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition-all', 'duration-200', 'cursor-pointer', 'styled');
} }
}); });
// Style textareas // Style textareas
document.querySelectorAll('textarea').forEach(textarea => { const textareas = document.querySelectorAll('textarea');
textareas.forEach(textarea => {
if (!textarea.classList.contains('styled')) { if (!textarea.classList.contains('styled')) {
textarea.className = 'w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400 styled'; // Remove any existing conflicting classes
textarea.className = textarea.className.replace(/form-control/g, 'styled');
textarea.className = textarea.className.replace(/textarea/g, '');
// Add base textarea styling
textarea.classList.add('w-full', 'px-4', 'py-3', 'rounded-xl', 'border-2', 'border-gray-200', 'text-gray-700', 'text-sm', 'sm:text-base', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition-all', 'duration-200', 'resize-y', 'min-h-[100px]', 'placeholder:text-gray-400', 'styled');
} }
}); });
@ -652,10 +776,16 @@ function openModal(modalId) {
modal.classList.remove('hidden'); modal.classList.remove('hidden');
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
// Style form fields after modal opens // Style form fields after modal opens with multiple retries
setTimeout(() => { setTimeout(function() {
styleFormFields(); styleFormFields();
}, 50); }, 100);
setTimeout(function() {
styleFormFields();
}, 300);
setTimeout(function() {
styleFormFields();
}, 500);
} }
} }
@ -691,7 +821,7 @@ async function copyJoinUrl() {
messageElement.classList.add('text-green-600'); messageElement.classList.add('text-green-600');
messageElement.classList.remove('text-red-600'); messageElement.classList.remove('text-red-600');
setTimeout(() => { setTimeout(function() {
messageElement.textContent = ''; messageElement.textContent = '';
}, 3000); }, 3000);
} catch (e) { } catch (e) {

View File

@ -11,25 +11,25 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-6"> <div class="container mx-auto px-3 sm:px-4 lg:px-8 py-6">
<!-- Breadcrumb --> <!-- Breadcrumb -->
<nav class="mb-6" aria-label="breadcrumb"> <nav class="mb-6" aria-label="breadcrumb">
<ol class="flex items-center gap-2 text-sm flex-wrap"> <ol class="flex items-center gap-2 text-sm flex-wrap">
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-kaauh-blue transition flex items-center gap-1"> <li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Home" %} <i data-lucide="home" class="w-4 h-4"></i> {% trans "Dashboard" %}
</a></li> </a></li>
<li class="text-gray-400">/</li> <li class="text-gray-400">/</li>
<li><a href="{% url 'job_list' %}" class="text-gray-500 hover:text-kaauh-blue transition">{% trans "Jobs" %}</a></li> <li><a href="{% url 'job_list' %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Jobs" %}</a></li>
<li class="text-gray-400">/</li> <li class="text-gray-400">/</li>
<li><a href="{% url 'job_detail' job.slug %}" class="text-gray-500 hover:text-kaauh-blue transition">{{ job.title }}</a></li> <li><a href="{% url 'job_detail' job.slug %}" class="text-gray-500 hover:text-temple-red transition">{{ job.title }}</a></li>
<li class="text-gray-400">/</li> <li class="text-gray-400">/</li>
<li class="text-temple-red font-semibold">{% trans "Applicants" %}</li> <li class="text-temple-red font-semibold">{% trans "Applications" %}</li>
</ol> </ol>
</nav> </nav>
<!-- Job Header --> <!-- Job Header -->
<div class="bg-gradient-to-br from-kaauh-blue to-[#004d57] text-white rounded-2xl p-6 mb-6"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white rounded-2xl p-6 mb-6">
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4"> <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
<div class="flex-1"> <div class="flex-1">
<h1 class="text-2xl font-bold mb-2">{{ job.title }}</h1> <h1 class="text-2xl font-bold mb-2">{{ job.title }}</h1>
@ -39,7 +39,7 @@
<div class="flex flex-wrap gap-4 text-sm"> <div class="flex flex-wrap gap-4 text-sm">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<i data-lucide="users" class="w-4 h-4"></i> <i data-lucide="users" class="w-4 h-4"></i>
<span>{{ total_applications }} {% trans "Total Applicants" %}</span> <span>{{ total_applications }} {% trans "Total Applicantions" %}</span>
</div> </div>
{% if job.max_applications %} {% if job.max_applications %}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@ -62,8 +62,8 @@
<!-- Stats Section --> <!-- Stats Section -->
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-6"> <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-6">
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 text-center border-l-4 border-kaauh-blue"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 text-center border-l-4 border-temple-red">
<div class="text-3xl font-bold text-kaauh-blue mb-1">{{ total_applications }}</div> <div class="text-3xl font-bold text-temple-red mb-1">{{ total_applications }}</div>
<div class="text-xs text-gray-600">{% trans "Total Applications" %}</div> <div class="text-xs text-gray-600">{% trans "Total Applications" %}</div>
</div> </div>
{% for stage_key, stage_data in stage_stats.items %} {% for stage_key, stage_data in stage_stats.items %}
@ -88,11 +88,11 @@
<label for="search" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Search Applicants" %}</label> <label for="search" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Search Applicants" %}</label>
<input type="text" id="search" name="q" value="{{ search_query }}" <input type="text" id="search" name="q" value="{{ search_query }}"
placeholder="{% trans 'Search by name, email, or phone...' %}" placeholder="{% trans 'Search by name, email, or phone...' %}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition"> class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
</div> </div>
<div> <div>
<label for="stage" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Application Stage" %}</label> <label for="stage" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Application Stage" %}</label>
<select id="stage" name="stage" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition"> <select id="stage" name="stage" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
<option value="">{% trans "All Stages" %}</option> <option value="">{% trans "All Stages" %}</option>
{% for key, value in stage_choices %} {% for key, value in stage_choices %}
<option value="{{ key }}" {% if stage_filter == key %}selected{% endif %}> <option value="{{ key }}" {% if stage_filter == key %}selected{% endif %}>
@ -105,27 +105,27 @@
<label for="min_ai_score" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Min AI Score" %}</label> <label for="min_ai_score" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Min AI Score" %}</label>
<input type="number" id="min_ai_score" name="min_ai_score" value="{{ min_ai_score }}" <input type="number" id="min_ai_score" name="min_ai_score" value="{{ min_ai_score }}"
placeholder="0" min="0" max="100" placeholder="0" min="0" max="100"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition"> class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
</div> </div>
<div> <div>
<label for="max_ai_score" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Max AI Score" %}</label> <label for="max_ai_score" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Max AI Score" %}</label>
<input type="number" id="max_ai_score" name="max_ai_score" value="{{ max_ai_score }}" <input type="number" id="max_ai_score" name="max_ai_score" value="{{ max_ai_score }}"
placeholder="100" min="0" max="100" placeholder="100" min="0" max="100"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition"> class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
</div> </div>
<div> <div>
<label for="date_from" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "From Date" %}</label> <label for="date_from" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "From Date" %}</label>
<input type="date" id="date_from" name="date_from" value="{{ date_from }}" <input type="date" id="date_from" name="date_from" value="{{ date_from }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition"> class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
</div> </div>
<div> <div>
<label for="date_to" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "To Date" %}</label> <label for="date_to" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "To Date" %}</label>
<input type="date" id="date_to" name="date_to" value="{{ date_to }}" <input type="date" id="date_to" name="date_to" value="{{ date_to }}"
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition"> class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
</div> </div>
<div> <div>
<label for="sort" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Sort By" %}</label> <label for="sort" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Sort By" %}</label>
<select id="sort" name="sort" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition"> <select id="sort" name="sort" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
<option value="-created_at" {% if sort_by == '-created_at' %}selected{% endif %}>{% trans "Newest First" %}</option> <option value="-created_at" {% if sort_by == '-created_at' %}selected{% endif %}>{% trans "Newest First" %}</option>
<option value="created_at" {% if sort_by == 'created_at' %}selected{% endif %}>{% trans "Oldest First" %}</option> <option value="created_at" {% if sort_by == 'created_at' %}selected{% endif %}>{% trans "Oldest First" %}</option>
<option value="person__first_name" {% if sort_by == 'person__first_name' %}selected{% endif %}>{% trans "Name (A-Z)" %}</option> <option value="person__first_name" {% if sort_by == 'person__first_name' %}selected{% endif %}>{% trans "Name (A-Z)" %}</option>
@ -135,7 +135,7 @@
</div> </div>
</div> </div>
<div class="flex gap-3"> <div class="flex gap-3">
<button type="submit" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md"> <button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
<i data-lucide="filter" class="w-4 h-4"></i> <i data-lucide="filter" class="w-4 h-4"></i>
{% trans "Apply Filters" %} {% trans "Apply Filters" %}
</button> </button>
@ -201,7 +201,7 @@
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<a href="{% url 'application_detail' application.slug %}" class="inline-flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white font-medium px-3 py-2 rounded-lg text-xs transition"> <a href="{% url 'application_detail' application.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-3 py-2 rounded-lg text-xs transition">
<i data-lucide="file-text" class="w-3 h-3"></i> <i data-lucide="file-text" class="w-3 h-3"></i>
{% trans "Application" %} {% trans "Application" %}
</a> </a>
@ -236,7 +236,7 @@
{% trans "There are currently no applicants for this job." %} {% trans "There are currently no applicants for this job." %}
{% endif %} {% endif %}
</p> </p>
<a href="{% url 'job_applicants' job.slug %}" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-6 py-3 rounded-xl text-sm transition shadow-sm hover:shadow-md"> <a href="{% url 'job_applicants' job.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-6 py-3 rounded-xl text-sm transition shadow-sm hover:shadow-md">
<i data-lucide="refresh-cw" class="w-5 h-5"></i> <i data-lucide="refresh-cw" class="w-5 h-5"></i>
{% trans "Clear Filters" %} {% trans "Clear Filters" %}
</a> </a>

View File

@ -11,7 +11,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-6"> <div class="container mx-auto px-3 sm:px-4 lg:px-8 py-6">
<!-- Breadcrumb --> <!-- Breadcrumb -->
<nav class="mb-6" aria-label="breadcrumb"> <nav class="mb-6" aria-label="breadcrumb">
@ -24,18 +24,18 @@
<li class="text-gray-400">/</li> <li class="text-gray-400">/</li>
<li><a href="{% url 'job_detail' job.slug %}" class="text-gray-500 hover:text-temple-red transition">{{ job.title }}</a></li> <li><a href="{% url 'job_detail' job.slug %}" class="text-gray-500 hover:text-temple-red transition">{{ job.title }}</a></li>
<li class="text-gray-400">/</li> <li class="text-gray-400">/</li>
<li class="text-temple-red font-semibold">{% trans "Applicants" %}</li> <li class="text-temple-red font-semibold">{% trans "Applications" %}</li>
</ol> </ol>
</nav> </nav>
<!-- Header --> <!-- Header -->
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6"> <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
<div> <div class="text-gray-900">
<h1 class="text-2xl font-bold text-gray-900 mb-1 flex items-center gap-3"> <h1 class="text-2xl font-bold mb-1 flex items-center gap-3">
<a href="{% url 'job_detail' job.slug %}" class="text-temple-red hover:text-[#7a1a29] transition"> <a href="{% url 'job_detail' job.slug %}" class="text-temple-red hover:text-[#7a1a29] transition">
<i data-lucide="arrow-left" class="w-6 h-6"></i> <i data-lucide="arrow-left" class="w-6 h-6"></i>
</a> </a>
{% trans "Applicants for" %} "{{ job.title }}" <span>{% trans "Applicants for" %} "{{ job.title }}"</span>
</h1> </h1>
</div> </div>
<a href="{% url 'application_create_for_job' job.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md whitespace-nowrap"> <a href="{% url 'application_create_for_job' job.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md whitespace-nowrap">

View File

@ -0,0 +1,60 @@
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 500 140"
height="{{ height }}"
width="{{ width }}"
class="{{ classes }}"
role="img"
aria-label="Tenhal Logo"
>
<defs>
<linearGradient id="redGradient_{{ uid }}" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#C41E3A;stop-opacity:1" />
<stop offset="100%" style="stop-color:#8B1228;stop-opacity:1" />
</linearGradient>
<filter id="shadow_{{ uid }}" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceAlpha" stdDeviation="2"/>
<feOffset dx="0" dy="2" result="offsetblur"/>
<feComponentTransfer>
<feFuncA type="linear" slope="0.15"/>
</feComponentTransfer>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<g transform="translate(40, 35)" filter="url(#shadow_{{ uid }})">
<path d="M 30 5 L 50 15 L 50 35 L 30 45 L 10 35 L 10 15 Z"
fill="url(#redGradient_{{ uid }})" stroke="none"/>
<path d="M 30 15 L 40 20 L 40 30 L 30 35 L 20 30 L 20 20 Z"
fill="none" stroke="#ffffff" stroke-width="2" opacity="0.8"/>
<circle cx="30" cy="23" r="3" fill="#ffffff"/>
<path d="M 24 30 Q 30 27 36 30"
fill="none" stroke="#ffffff" stroke-width="2" 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>
<rect x="95" y="35" width="2" height="55" fill="#E0E0E0"/>
<text x="125" y="62"
font-family="'Plus Jakarta Sans', Arial, sans-serif"
font-size="38" font-weight="300" fill="#2C2C2C" letter-spacing="4">TENHAL</text>
<rect x="125" y="72" width="55" height="2.5" fill="#C41E3A"/>
<text x="125" y="92"
font-family="'Plus Jakarta Sans', Arial, sans-serif"
font-size="13" font-weight="400" fill="#666666" letter-spacing="2.5">APPLICANT TRACKING SYSTEM</text>
<text x="125" y="108"
font-family="'Plus Jakarta Sans', Arial, sans-serif"
font-size="10" font-weight="300" fill="#999999" letter-spacing="1" font-style="italic">Streamline Your Hiring Process</text>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,58 +1,63 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n %}
{% block title %}{{ title }} - ATS{% endblock %} {% block title %}{{ title }} - {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container mx-auto px-4 py-8">
<div class="row justify-content-center"> <div class="flex justify-center">
<div class="col-md-6"> <div class="w-full max-w-2xl">
<div class="card"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="card-header bg-warning text-white"> <div class="bg-amber-500 text-white p-5">
<h5 class="mb-0"> <h5 class="text-lg font-bold flex items-center gap-2">
<i class="fas fa-exclamation-triangle me-2"></i> <i data-lucide="alert-triangle" class="w-6 h-6"></i>
{{ title }} {{ title }}
</h5> </h5>
</div> </div>
<div class="card-body"> <div class="p-6">
<div class="alert alert-warning" role="alert"> <div class="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-6 flex items-start gap-3">
<i class="fas fa-info-circle me-2"></i> <i data-lucide="info" class="w-5 h-5 text-amber-600 shrink-0 mt-0.5"></i>
{{ message }} <p class="text-amber-800">{{ message }}</p>
</div> </div>
<div class="d-flex align-items-center mb-3"> <div class="bg-gray-50 rounded-xl p-5 mb-6">
<div class="me-3"> <div class="flex flex-wrap items-center gap-4 mb-4">
<strong>{% trans "Agency:" %}</strong> {{ access_link.assignment.agency.name }} <div>
<strong class="text-gray-700">{% trans "Agency:" %}</strong> {{ access_link.assignment.agency.name }}
</div> </div>
<div class="me-3"> <div>
<strong>{% trans "Job:" %}</strong> {{ access_link.assignment.job.title }} <strong class="text-gray-700">{% trans "Job:" %}</strong> {{ access_link.assignment.job.title }}
</div> </div>
</div> </div>
<div class="d-flex align-items-center mb-3"> <div class="flex flex-wrap items-center gap-4 mb-4">
<div class="me-3"> <div>
<strong>{% trans "Current Status:" %}</strong> <strong class="text-gray-700">{% trans "Current Status:" %}</strong>
<span class="badge bg-{{ 'success' if access_link.is_active else 'danger' }}"> <span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ 'bg-green-100 text-green-800' if access_link.is_active else 'bg-red-100 text-red-800' }}">
{{ 'Active' if access_link.is_active else 'Inactive' }} {% if access_link.is_active %}
{% trans "Active" %}
{% else %}
{% trans "Inactive" %}
{% endif %}
</span> </span>
</div> </div>
</div> </div>
<div class="d-flex align-items-center mb-3"> <div class="flex flex-wrap items-center gap-4 mb-4">
<div class="me-3"> <div>
<strong>{% trans "Expires:" %}</strong> {{ access_link.expires_at|date:"Y-m-d H:i" }} <strong class="text-gray-700">{% trans "Expires:" %}</strong> {{ access_link.expires_at|date:"Y-m-d H:i" }}
</div> </div>
</div> </div>
<div class="d-flex align-items-center mb-3"> <div class="flex flex-wrap items-center gap-4 mb-4">
<div class="me-3"> <div>
<strong>{% trans "Access Count:" %}</strong> {{ access_link.access_count }} <strong class="text-gray-700">{% trans "Access Count:" %}</strong> {{ access_link.access_count }}
</div> </div>
</div> </div>
<div class="d-flex align-items-center mb-3"> <div class="flex flex-wrap items-center gap-4">
<div class="me-3"> <div>
<strong>{% trans "Last Accessed:" %}</strong> <strong class="text-gray-700">{% trans "Last Accessed:" %}</strong>
{% if access_link.last_accessed %} {% if access_link.last_accessed %}
{{ access_link.last_accessed|date:"Y-m-d H:i" }} {{ access_link.last_accessed|date:"Y-m-d H:i" }}
{% else %} {% else %}
@ -60,16 +65,17 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
<form method="post" action="{% url request.resolver_match.url_name %}"> <form method="post" action="{% url request.resolver_match.url_name %}">
{% csrf_token %} {% csrf_token %}
<div class="d-grid gap-2 d-md-flex justify-content-md-end"> <div class="flex flex-col sm:flex-row gap-3 justify-end">
<a href="{{ cancel_url }}" class="btn btn-secondary"> <a href="{{ cancel_url }}" class="inline-flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-6 py-2.5 rounded-xl transition">
<i class="fas fa-times me-2"></i> <i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel" %} {% trans "Cancel" %}
</a> </a>
<button type="submit" class="btn btn-warning"> <button type="submit" class="inline-flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
<i class="fas fa-{{ 'toggle-on' if title == 'Reactivate Access Link' else 'toggle-off' }} me-2"></i> <i data-lucide="{% if title == 'Reactivate Access Link' %}toggle-right{% else %}toggle-left{% endif %}" class="w-4 h-4"></i>
{{ title }} {{ title }}
</button> </button>
</div> </div>
@ -79,4 +85,8 @@
</div> </div>
</div> </div>
</div> </div>
<script>
lucide.createIcons();
</script>
{% endblock %} {% endblock %}

View File

@ -4,184 +4,151 @@
{% block title %}{% trans "Access Link Details" %} - ATS{% endblock %} {% block title %}{% trans "Access Link Details" %} - ATS{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container mx-auto px-4 py-8">
<div class="d-flex justify-content-between align-items-center mb-4 px-3 py-3"> <!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6 px-3 py-3">
<div> <div>
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
<i class="fas fa-link me-2"></i> <div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="link" class="w-8 h-8 text-temple-red"></i>
</div>
{% trans "Access Link Details" %} {% trans "Access Link Details" %}
</h1> </h1>
<p class="text-muted mb-0">{% trans "Secure access link for agency candidate submissions" %}</p> <p class="text-gray-600">{% trans "Secure access link for agency candidate submissions" %}</p>
</div> </div>
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="inline-flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-6 py-2.5 rounded-xl transition">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Assignment" %} <i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Assignment" %}
</a> </a>
</div> </div>
<div class="row"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<div class="col-md-8"> <!-- Main Content -->
<div class="kaauh-card shadow-sm mb-4 px-3 py-3"> <div class="lg:col-span-2">
<div class="card-body"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
<div class="d-flex justify-content-between align-items-start mb-3"> <div class="p-6">
<h5 class="card-title mb-0"> <div class="flex justify-between items-start mb-6">
<i class="fas fa-shield-alt me-2 text-primary"></i> <h5 class="text-xl font-bold text-gray-900 flex items-center gap-2">
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<i data-lucide="shield" class="w-5 h-5 text-blue-600"></i>
</div>
{% trans "Access Information" %} {% trans "Access Information" %}
</h5> </h5>
<span class="badge {% if access_link.is_active %}bg-success{% else %}bg-danger{% endif %}"> <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {% if access_link.is_active %}bg-green-100 text-green-800{% else %}bg-red-100 text-red-800{% endif %}">
{% if access_link.is_active %}{% trans "Active" %}{% else %}{% trans "Inactive" %}{% endif %} {% if access_link.is_active %}{% trans "Active" %}{% else %}{% trans "Inactive" %}{% endif %}
</span> </span>
</div> </div>
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="col-md-6 mb-3"> <div>
<label class="form-label text-muted small">{% trans "Assignment" %}</label> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Assignment" %}</label>
<div class="fw-semibold"> <div class="font-semibold text-gray-900">
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="text-decoration-none"> <a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="hover:text-temple-red transition">
{{ access_link.assignment.agency.name }} - {{ access_link.assignment.job.title }} {{ access_link.assignment.agency.name }} - {{ access_link.assignment.job.title }}
</a> </a>
</div> </div>
</div> </div>
<div class="col-md-6 mb-3"> <div>
<label class="form-label text-muted small">{% trans "Agency" %}</label> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Agency" %}</label>
<div class="fw-semibold">{{ access_link.assignment.agency.name }}</div> <div class="font-semibold text-gray-900">{{ access_link.assignment.agency.name }}</div>
</div> </div>
<div class="col-md-6 mb-3"> <div>
<label class="form-label text-muted small">{% trans "Created At" %}</label> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Created At" %}</label>
<div class="fw-semibold">{{ access_link.created_at|date:"Y-m-d H:i" }}</div> <div class="font-semibold text-gray-900">{{ access_link.created_at|date:"Y-m-d H:i" }}</div>
</div> </div>
<div class="col-md-6 mb-3"> <div>
<label class="form-label text-muted small">{% trans "Expires At" %}</label> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Expires At" %}</label>
<div class="fw-semibold {% if access_link.is_expired %}text-danger{% endif %}"> <div class="font-semibold {% if access_link.is_expired %}text-red-600{% endif %} text-gray-900">
{{ access_link.expires_at|date:"Y-m-d H:i" }} {{ access_link.expires_at|date:"Y-m-d H:i" }}
{% if access_link.is_expired %} {% if access_link.is_expired %}
<span class="badge bg-danger ms-2">{% trans "Expired" %}</span> <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-800 ml-2">{% trans "Expired" %}</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="col-md-6 mb-3"> <div>
<label class="form-label text-muted small">{% trans "Max Candidates" %}</label> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Max Candidates" %}</label>
<div class="fw-semibold">{{ access_link.assignment.max_candidates }}</div> <div class="font-semibold text-gray-900">{{ access_link.assignment.max_candidates }}</div>
</div> </div>
<div class="col-md-6 mb-3"> <div>
<label class="form-label text-muted small">{% trans "Candidates Submitted" %}</label> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Candidates Submitted" %}</label>
<div class="fw-semibold">{{ access_link.assignment.candidates_submitted }}</div> <div class="font-semibold text-gray-900">{{ access_link.assignment.candidates_submitted }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% comment %} <div class="kaauh-card shadow-sm"> <!-- Sidebar -->
<div class="card-body px-3 py-3"> <div class="lg:col-span-1 space-y-6">
<h5 class="card-title mb-3"> <!-- Usage Statistics -->
<i class="fas fa-key me-2 text-warning"></i> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
{% trans "Access Credentials" %} <div class="p-6">
</h5> <h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<div class="mb-3"> <i data-lucide="trending-up" class="w-5 h-5 text-blue-600"></i>
<label class="form-label text-muted small">{% trans "Login URL" %}</label>
<div class="input-group">
<input type="text" readonly value="{{ request.scheme }}://{{ request.get_host }}{% url 'agency_portal_login' %}"
class="form-control font-monospace" id="loginUrl">
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('loginUrl')">
<i class="fas fa-copy"></i>
</button>
</div> </div>
</div>
<div class="mb-3">
<label class="form-label text-muted small">{% trans "Access Token" %}</label>
<div class="input-group">
<input type="text" readonly value="{{ access_link.unique_token }}"
class="form-control font-monospace" id="accessToken">
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessToken')">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label text-muted small">{% trans "Password" %}</label>
<div class="input-group">
<input type="text" readonly value="{{ access_link.access_password }}"
class="form-control font-monospace" id="accessPassword">
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessPassword')">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
{% trans "Share these credentials securely with the agency. They can use this information to log in and submit candidates." %}
</div>
</div>
</div> {% endcomment %}
</div>
<div class="col-md-4">
<div class="kaauh-card shadow-sm mb-4 px-3 py-3">
<div class="card-body">
<h5 class="card-title mb-3">
<i class="fas fa-chart-line me-2 text-info"></i>
{% trans "Usage Statistics" %} {% trans "Usage Statistics" %}
</h5> </h5>
<div class="mb-3"> <div class="space-y-4">
<div class="d-flex justify-content-between align-items-center mb-2"> <div class="flex justify-between items-center">
<span class="text-muted">{% trans "Total Accesses" %}</span> <span class="text-gray-600">{% trans "Total Accesses" %}</span>
<span class="fw-semibold">{{ access_link.access_count }}</span> <span class="font-bold text-gray-900">{{ access_link.access_count }}</span>
</div> </div>
<div class="d-flex justify-content-between align-items-center mb-2"> <div class="flex justify-between items-center">
<span class="text-muted">{% trans "Last Accessed" %}</span> <span class="text-gray-600">{% trans "Last Accessed" %}</span>
<span class="fw-semibold"> <span class="font-semibold text-gray-900">
{% if access_link.last_accessed %} {% if access_link.last_accessed %}
{{ access_link.last_accessed|date:"Y-m-d H:i" }} {{ access_link.last_accessed|date:"Y-m-d H:i" }}
{% else %} {% else %}
<span class="text-muted">{% trans "Never" %}</span> <span class="text-gray-500">{% trans "Never" %}</span>
{% endif %} {% endif %}
</span> </span>
</div> </div>
<div class="d-flex justify-content-between align-items-center"> <div class="flex justify-between items-center">
<span class="text-muted">{% trans "Submissions" %}</span> <span class="text-gray-600">{% trans "Submissions" %}</span>
<span class="fw-semibold">{{ access_link.assignment.candidates_submitted }}/{{ access_link.assignment.max_candidates }}</span> <span class="font-bold text-gray-900">{{ access_link.assignment.candidates_submitted }}/{{ access_link.assignment.max_candidates }}</span>
</div> </div>
</div> </div>
<div class="progress" style="height: 8px;">
{% widthratio access_link.assignment.candidates_submitted access_link.assignment.max_candidates 100 as progress_percent %} {% widthratio access_link.assignment.candidates_submitted access_link.assignment.max_candidates 100 as progress_percent %}
<div class="progress-bar {% if progress_percent >= 80 %}bg-danger{% elif progress_percent >= 60 %}bg-warning{% else %}bg-success{% endif %}" <div class="mt-4">
style="width: {{ progress_percent }}%"></div> <div class="w-full bg-gray-200 rounded-full h-2">
<div class="h-2 rounded-full {% if progress_percent >= 80 %}bg-red-500{% elif progress_percent >= 60 %}bg-yellow-500{% else %}bg-green-500{% endif %}" style="width: {{ progress_percent }}%"></div>
</div>
<div class="text-right text-sm text-gray-600 mt-1">{{ progress_percent }}%</div>
</div> </div>
</div> </div>
</div> </div>
<div class="kaauh-card shadow-sm px-3 py-3"> <!-- Actions -->
<div class="card-body"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<h5 class="card-title mb-3"> <div class="p-6">
<i class="fas fa-cog me-2 text-secondary"></i> <h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
<div class="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center">
<i data-lucide="settings" class="w-5 h-5 text-gray-600"></i>
</div>
{% trans "Actions" %} {% trans "Actions" %}
</h5> </h5>
<div class="d-grid gap-2"> <div class="space-y-3">
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="btn btn-outline-secondary btn-sm"> <a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="inline-flex items-center justify-center w-full gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2.5 rounded-xl transition">
<i class="fas fa-eye me-1"></i> {% trans "View Assignment" %} <i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Assignment" %}
</a> </a>
{% if access_link.is_active and not access_link.is_expired %} {% if access_link.is_active and not access_link.is_expired %}
<button class="btn btn-warning btn-sm" onclick="confirmDeactivate()"> <button onclick="confirmDeactivate()" class="inline-flex items-center justify-center w-full gap-2 bg-yellow-500 hover:bg-yellow-600 text-white font-semibold px-4 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
<i class="fas fa-pause me-1"></i> {% trans "Deactivate" %} <i data-lucide="pause" class="w-4 h-4"></i> {% trans "Deactivate" %}
</button> </button>
{% endif %} {% endif %}
{% if access_link.is_expired or not access_link.is_active %} {% if access_link.is_expired or not access_link.is_active %}
<button class="btn btn-success btn-sm" onclick="confirmReactivate()"> <button onclick="confirmReactivate()" class="inline-flex items-center justify-center w-full gap-2 bg-green-500 hover:bg-green-600 text-white font-semibold px-4 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
<i class="fas fa-play me-1"></i> {% trans "Reactivate" %} <i data-lucide="play" class="w-4 h-4"></i> {% trans "Reactivate" %}
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -190,41 +157,22 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %}
{% block customJS %}
<script> <script>
function copyToClipboard(elementId) {
const element = document.getElementById(elementId);
element.select();
document.execCommand('copy');
// Show feedback
const button = element.nextElementSibling;
const originalHTML = button.innerHTML;
button.innerHTML = '<i class="fas fa-check"></i>';
button.classList.add('btn-success');
button.classList.remove('btn-outline-secondary');
setTimeout(() => {
button.innerHTML = originalHTML;
button.classList.remove('btn-success');
button.classList.add('btn-outline-secondary');
}, 2000);
}
function confirmDeactivate() { function confirmDeactivate() {
if (confirm('{% trans "Are you sure you want to deactivate this access link? Agencies will no longer be able to use it." %}')) { if (confirm('{% trans "Are you sure you want to deactivate this access link? Agencies will no longer be able to use it." %}')) {
// Submit form to deactivate
window.location.href = '{% url "agency_access_link_deactivate" access_link.slug %}'; window.location.href = '{% url "agency_access_link_deactivate" access_link.slug %}';
} }
} }
function confirmReactivate() { function confirmReactivate() {
if (confirm('{% trans "Are you sure you want to reactivate this access link?" %}')) { if (confirm('{% trans "Are you sure you want to reactivate this access link?" %}')) {
// Submit form to reactivate
window.location.href = '{% url "agency_access_link_reactivate" access_link.slug %}'; window.location.href = '{% url "agency_access_link_reactivate" access_link.slug %}';
} }
} }
document.addEventListener('DOMContentLoaded', function() {
lucide.createIcons();
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -3,284 +3,159 @@
{% block title %}{{ assignment.agency.name }} - {{ assignment.job.title }} - ATS{% endblock %} {% block title %}{{ assignment.agency.name }} - {{ assignment.job.title }} - ATS{% endblock %}
{% block customCSS %}
<style>
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745;
--kaauh-info: #17a2b8;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.status-badge {
font-size: 0.75rem;
padding: 0.3em 0.7em;
border-radius: 0.35rem;
font-weight: 700;
}
.status-ACTIVE { background-color: var(--kaauh-success); color: white; }
.status-EXPIRED { background-color: var(--kaauh-danger); color: white; }
.status-COMPLETED { background-color: var(--kaauh-info); color: white; }
.status-CANCELLED { background-color: var(--kaauh-warning); color: #856404; }
.progress-ring {
width: 120px;
height: 120px;
position: relative;
}
.progress-ring-circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.progress-ring-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.5rem;
font-weight: 700;
color: var(--kaauh-teal-dark);
}
.message-item {
border-left: 4px solid var(--kaauh-teal);
background-color: #f8f9fa;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 0 0.5rem 0.5rem 0;
}
.message-item.unread {
border-left-color: var(--kaauh-info);
background-color: #e7f3ff;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="px-4 py-6">
<!-- Header --> <!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-6">
<div> <div class="flex-1">
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="text-2xl font-bold text-temple-dark mb-1 flex items-center gap-2">
<i class="fas fa-tasks me-2"></i> <i data-lucide="tasks" class="w-7 h-7"></i>
{{ assignment.agency.name }} - {{ assignment.job.title }} {{ assignment.agency.name }} - {{ assignment.job.title }}
</h1> </h1>
<p class="text-muted mb-0"> <p class="text-gray-600">
{% trans "Assignment Details and Management" %} {% trans "Assignment Details and Management" %}
</p> </p>
</div> </div>
<div> <div class="flex gap-2">
<a href="{% url 'agency_assignment_list' %}" class="btn btn-outline-secondary me-2"> <a href="{% url 'agency_assignment_list' %}"
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Assignments" %} class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Back to Assignments" %}
</a> </a>
<a href="{% url 'agency_assignment_update' assignment.slug %}" class="btn btn-main-action"> <a href="{% url 'agency_assignment_update' assignment.slug %}"
<i class="fas fa-edit me-1"></i> {% trans "Edit Assignment" %} class="inline-flex items-center gap-2 bg-temple-red hover:bg-red-800 text-white font-semibold px-4 py-2 rounded-lg transition shadow-md hover:shadow-lg">
<i data-lucide="edit" class="w-4 h-4"></i>
{% trans "Edit Assignment" %}
</a> </a>
</div> </div>
</div> </div>
<div class="row"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Main Content -->
<!-- Assignment Overview --> <div class="lg:col-span-2 space-y-6">
<div class="col-lg-8">
<!-- Assignment Details Card --> <!-- Assignment Details Card -->
<div class="kaauh-card p-4 mb-4"> <div class="bg-white rounded-xl shadow-md border border-gray-200">
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);"> <div class="p-6">
<i class="fas fa-info-circle me-2"></i> <h5 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
<i data-lucide="info" class="w-5 h-5"></i>
{% trans "Assignment Details" %} {% trans "Assignment Details" %}
</h5> </h5>
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="col-md-6"> <div class="space-y-4">
<div class="mb-3">
<label class="text-muted small">{% trans "Agency" %}</label>
<div class="fw-bold">{{ assignment.agency.name }}</div>
<div class="text-muted small">{{ assignment.agency.contact_person }}</div>
</div>
<div class="mb-3">
<label class="text-muted small">{% trans "Job" %}</label>
<div class="fw-bold">{{ assignment.job.title }}</div>
<div class="text-muted small">{{ assignment.job.department }}</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="text-muted small">{% trans "Status" %}</label>
<div> <div>
<span class="status-badge bg-primary-theme text-white"> <label class="text-sm text-gray-500 font-medium block mb-1">{% trans "Agency" %}</label>
<div class="font-semibold text-gray-900">{{ assignment.agency.name }}</div>
<div class="text-sm text-gray-600">{{ assignment.agency.contact_person }}</div>
</div>
<div>
<label class="text-sm text-gray-500 font-medium block mb-1">{% trans "Job" %}</label>
<div class="font-semibold text-gray-900">{{ assignment.job.title }}</div>
<div class="text-sm text-gray-600">{{ assignment.job.department }}</div>
</div>
</div>
<div class="space-y-4">
<div>
<label class="text-sm text-gray-500 font-medium block mb-1">{% trans "Status" %}</label>
<span class="status-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-temple-red text-white">
{{ assignment.get_status_display }} {{ assignment.get_status_display }}
</span> </span>
</div> </div>
</div> <div>
<div class="mb-3"> <label class="text-sm text-gray-500 font-medium block mb-1">{% trans "Deadline" %}</label>
<label class="text-muted small">{% trans "Deadline" %}</label> <div class="{% if assignment.is_expired %}text-red-600{% else %}text-gray-600{% endif %} flex items-center gap-1">
<div class="{% if assignment.is_expired %}text-danger{% else %}text-muted{% endif %}"> <i data-lucide="calendar" class="w-4 h-4"></i>
<i class="fas fa-calendar-alt me-1"></i>
{{ assignment.deadline_date|date:"Y-m-d H:i" }} {{ assignment.deadline_date|date:"Y-m-d H:i" }}
</div> </div>
{% if assignment.is_expired %} {% if assignment.is_expired %}
<small class="text-danger"> <span class="text-sm text-red-600 flex items-center gap-1 mt-1">
<i class="fas fa-exclamation-triangle me-1"></i>{% trans "Expired" %} <i data-lucide="alert-triangle" class="w-4 h-4"></i>
</small> {% trans "Expired" %}
</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
{% if assignment.admin_notes %} {% if assignment.admin_notes %}
<div class="mt-3 pt-3 border-top"> <div class="mt-6 pt-4 border-t border-gray-200">
<label class="text-muted small">{% trans "Admin Notes" %}</label> <label class="text-sm text-gray-500 font-medium block mb-2">{% trans "Admin Notes" %}</label>
<div class="text-muted">{{ assignment.admin_notes }}</div> <div class="text-gray-700">{{ assignment.admin_notes }}</div>
</div> </div>
{% endif %} {% endif %}
</div>
{% comment %} <div class="kaauh-card shadow-sm mb-4">
<div class="card-body my-2">
<h5 class="card-title mb-3 mx-2">
<i class="fas fa-key me-2 text-warning"></i>
{% trans "Access Credentials" %}
</h5>
<div class="mb-3 mx-2">
<label class="form-label text-muted small">{% trans "Login URL" %}</label>
<div class="input-group">
<input type="text" readonly value="{{ request.scheme }}://{{ request.get_host }}{% url 'agency_portal_login' %}"
class="form-control font-monospace" id="loginUrl">
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('loginUrl')">
<i class="fas fa-copy"></i>
</button>
</div> </div>
</div> </div>
<div class="mb-3 mx-2">
<label class="form-label text-muted small">{% trans "Access Token" %}</label>
<div class="input-group">
<input type="text" readonly value="{{ access_link.unique_token }}"
class="form-control font-monospace" id="accessToken">
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessToken')">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="mb-3 mx-2">
<label class="form-label text-muted small">{% trans "Password" %}</label>
<div class="input-group">
<input type="text" readonly value="{{ access_link.access_password }}"
class="form-control font-monospace" id="accessPassword">
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessPassword')">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
<div class="alert alert-info mx-2">
<i class="fas fa-info-circle me-2"></i>
{% trans "Share these credentials securely with the agency. They can use this information to log in and submit applications." %}
</div>
{% if access_link %}
<a href="{% url 'agency_access_link_detail' access_link.slug %}"
class="btn btn-outline-info btn-sm mx-2">
<i class="fas fa-eye me-1"></i> {% trans "View Access Links Details" %}
</a>
{% endif %}
</div>
</div> {% endcomment %}
<!-- Applications Card --> <!-- Applications Card -->
<div class="kaauh-card p-4"> <div class="bg-white rounded-xl shadow-md border border-gray-200">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="p-6">
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);"> <div class="flex justify-between items-center mb-4">
<i class="fas fa-users me-2"></i> <h5 class="text-lg font-semibold text-temple-dark flex items-center gap-2">
<i data-lucide="users" class="w-5 h-5"></i>
{% trans "Submitted Applications" %} ({{ total_applications }}) {% trans "Submitted Applications" %} ({{ total_applications }})
</h5> </h5>
{% if access_link %} {% if access_link %}
<a href="{% url 'agency_portal_login' %}" target="_blank" class="btn btn-outline-info btn-sm"> <a href="{% url 'agency_portal_login' %}" target="_blank"
<i class="fas fa-external-link-alt me-1"></i> {% trans "Preview Portal" %} class="inline-flex items-center gap-2 border border-blue-500 text-blue-600 hover:bg-blue-50 px-3 py-1.5 rounded-lg text-sm font-medium transition">
<i data-lucide="external-link" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Preview Portal" %}</span>
</a> </a>
{% endif %} {% endif %}
</div> </div>
{% if applications %} {% if applications %}
<div class="table-responsive"> <div class="overflow-x-auto">
<table class="table table-hover"> <table class="w-full">
<thead> <thead>
<tr> <tr class="border-b border-gray-200">
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Application"%}</th> <th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Contact" %} {% trans "Application" %}
</th> </th>
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Stage" %} <th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
{% trans "Contact" %}
</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
{% trans "Stage" %}
</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
{% trans "Submitted" %}
</th>
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">
{% trans "Actions" %}
</th> </th>
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Submitted"%}</th>
<th class="px-4 py-3 text-uppercase small fw-bold text-muted text-end">{% trans "Actions" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="divide-y divide-gray-200">
{% for application in applications %} {% for application in applications %}
<tr> <tr class="hover:bg-gray-50 transition">
<td> <td class="px-4 py-3">
<div class="fw-bold">{{ application.name }}</div> <div class="font-semibold text-gray-900">{{ application.name }}</div>
</td> </td>
<td> <td class="px-4 py-3">
<div class="small"> <div class="text-sm text-gray-600 space-y-1">
<div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div> <div class="flex items-center gap-1">
<div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div> <i data-lucide="mail" class="w-3 h-3"></i>
{{ application.email }}
</div>
<div class="flex items-center gap-1">
<i data-lucide="phone" class="w-3 h-3"></i>
{{ application.phone }}
</div>
</div> </div>
</td> </td>
<td> <td class="px-4 py-3">
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span> <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-temple-red text-white">
{{ application.get_stage_display }}
</span>
</td> </td>
<td> <td class="px-4 py-3">
<div class="small text-muted"> <span class="text-sm text-gray-600">{{ application.created_at|date:"M d, Y" }}</span>
<div class="mb-1"><i class="fas fa-envelope me-2 w-20"></i>
{{application.email }}</div>
<div><i class="fas fa-phone me-2 w-20"></i>{{ application.phone }}</div>
</div>
</td> </td>
<td class="px-4"> <td class="px-4 py-3 text-right">
<span class="badge bg-primary-theme px-3">
{{application.get_stage_display }}</span>
</td>
<td class="px-4">
<span class="small text-muted">{{ application.created_at|date:"M d, Y" }}</span>
</td>
<td class="px-4 text-end">
<a href="{% url 'application_detail' application.slug %}" <a href="{% url 'application_detail' application.slug %}"
class="btn btn-sm btn-outline-primary" title="{% trans 'View Details' %}"> class="inline-flex items-center justify-center p-2 text-temple-red hover:bg-red-50 rounded-lg transition"
<i class="fas fa-eye"></i> title="{% trans 'View Details' %}">
<i data-lucide="eye" class="w-4 h-4"></i>
</a> </a>
</td> </td>
</tr> </tr>
@ -289,93 +164,94 @@
</table> </table>
</div> </div>
{% else %} {% else %}
<div class="text-center py-4"> <div class="text-center py-12">
<i class="fas fa-users fa-2x text-muted mb-3"></i> <i data-lucide="users" class="w-16 h-16 text-gray-400 mx-auto mb-4"></i>
<h6 class="text-muted">{% trans "No applications submitted yet" %}</h6> <h6 class="text-lg font-semibold text-gray-600 mb-2">{% trans "No applications submitted yet" %}</h6>
<p class="text-muted small"> <p class="text-gray-500 text-sm">
{% trans "Applications will appear here once the agency submits them through their portal." %} {% trans "Applications will appear here once agency submits them through their portal." %}
</p> </p>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
<!-- Sidebar --> <!-- Sidebar -->
<div class="col-lg-4"> <div class="space-y-6">
<!-- Progress Card --> <!-- Progress Card -->
<div class="kaauh-card p-4 mb-4"> <div class="bg-white rounded-xl shadow-md border border-gray-200 p-6">
<h5 class="mb-4 text-center" style="color: var(--kaauh-teal-dark);"> <h5 class="text-lg font-semibold text-temple-dark mb-4 text-center">
{% trans "Submission Progress" %} {% trans "Submission Progress" %}
</h5> </h5>
<div class="text-center mb-3"> <div class="text-center mb-4">
<div class="progress-ring"> <div class="relative inline-block">
<svg width="120" height="120"> <svg width="120" height="120" class="transform -rotate-90">
<circle class="progress-ring-circle" <circle cx="60" cy="60" r="52"
stroke="#e9ecef" stroke="#e5e7eb"
stroke-width="8"
fill="transparent"/>
<circle cx="60" cy="60" r="52"
stroke="#9d2235"
stroke-width="8" stroke-width="8"
fill="transparent" fill="transparent"
r="52" stroke-linecap="round"
cx="60"
cy="60"/>
<circle class="progress-ring-circle"
stroke="var(--kaauh-teal)"
stroke-width="8"
fill="transparent"
r="52"
cx="60"
cy="60"
style="stroke-dasharray: 326.73; stroke-dashoffset: {{ stroke_dashoffset }};"/> style="stroke-dasharray: 326.73; stroke-dashoffset: {{ stroke_dashoffset }};"/>
</svg> </svg>
<div class="position-absolute top-50 start-50 translate-middle text-center"> <div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center">
<div class="h3 fw-bold mb-0 text-dark">{{ total_applications }}</div> <div class="text-3xl font-bold text-gray-900">{{ total_applications }}</div>
<div class="small text-muted text-uppercase">{% trans "of" %} {{ assignment.max_candidates}}</div> <div class="text-xs text-gray-500 uppercase">{% trans "of" %} {{ assignment.max_candidates }}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="text-center"> <div class="text-center mb-4">
<div class="h4 mb-1">{{ total_applications }}</div> <div class="text-4xl font-bold text-temple-red mb-1">{{ total_applications }}</div>
<div class="text-muted">/ {{ assignment.max_candidates }} {% trans "applications" %}</div> <div class="text-gray-600">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
</div> </div>
<div class="progress mt-3 " style="height: 8px;">
{% widthratio total_applications assignment.max_candidates 100 as progress %} {% widthratio total_applications assignment.max_candidates 100 as progress %}
<div class="progress-bar bg-primary-theme" style="width: {{ progress }}%"></div> <div class="w-full bg-gray-200 rounded-full h-2">
<div class="bg-temple-red h-2 rounded-full transition-all duration-500" style="width: {{ progress }}%"></div>
</div> </div>
</div> </div>
<!-- Actions Card --> <!-- Actions Card -->
<div class="kaauh-card p-4"> <div class="bg-white rounded-xl shadow-md border border-gray-200 p-6">
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);"> <h5 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
<i class="fas fa-cog me-2"></i> <i data-lucide="settings" class="w-5 h-5"></i>
{% trans "Actions" %} {% trans "Actions" %}
</h5> </h5>
<div class="d-grid gap-2"> <div class="space-y-2">
<a href="{% url "message_list" %}" <a href="{% url "message_list" %}"
class="btn btn-outline-primary"> class="w-full inline-flex items-center justify-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg text-sm font-medium transition">
<i class="fas fa-envelope me-1"></i> {% trans "Send Message" %} <i data-lucide="mail" class="w-4 h-4"></i>
{% trans "Send Message" %}
</a> </a>
{% if assignment.is_active and not assignment.is_expired %} {% if assignment.is_active and not assignment.is_expired %}
<button type="button" class="btn btn-outline-warning" <button type="button"
data-bs-toggle="modal" data-bs-target="#extendDeadlineModal"> class="w-full inline-flex items-center justify-center gap-2 border border-yellow-500 text-yellow-700 hover:bg-yellow-50 px-4 py-2.5 rounded-lg text-sm font-medium transition"
<i class="fas fa-clock me-1"></i> {% trans "Extend Deadline" %} onclick="document.getElementById('extendDeadlineModal').classList.remove('hidden')">
<i data-lucide="clock" class="w-4 h-4"></i>
{% trans "Extend Deadline" %}
</button> </button>
{% endif %} {% endif %}
{% if assignment.is_active and assignment.status == 'ACTIVE' %} {% if assignment.is_active and assignment.status == 'ACTIVE' %}
<button type="button" class="btn btn-danger" <button type="button"
data-bs-toggle="modal" data-bs-target="#cancelAssignmentModal"> class="w-full inline-flex items-center justify-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2.5 rounded-lg text-sm font-medium transition"
<i class="fas fa-times me-1"></i> {% trans "Cancel Assignment" %} onclick="document.getElementById('cancelAssignmentModal').classList.remove('hidden')">
<i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel Assignment" %}
</button> </button>
{% endif %} {% endif %}
<a href="{% url 'agency_assignment_update' assignment.slug %}" <a href="{% url 'agency_assignment_update' assignment.slug %}"
class="btn btn-outline-secondary"> class="w-full inline-flex items-center justify-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg text-sm font-medium transition">
<i class="fas fa-edit me-1"></i> {% trans "Edit Assignment" %} <i data-lucide="edit-2" class="w-4 h-4"></i>
{% trans "Edit Assignment" %}
</a> </a>
</div> </div>
</div> </div>
@ -384,35 +260,35 @@
<!-- Messages Section --> <!-- Messages Section -->
{% if messages_ %} {% if messages_ %}
<div class="kaauh-card p-4 mt-4"> <div class="bg-white rounded-xl shadow-md border border-gray-200 p-6 mt-6">
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);"> <h5 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
<i class="fas fa-comments me-2"></i> <i data-lucide="message-square" class="w-5 h-5"></i>
{% trans "Recent Messages" %} {% trans "Recent Messages" %}
</h5> </h5>
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
{% for message in messages_|slice:":6" %} {% for message in messages_|slice:":6" %}
<div class="col-lg-6 mb-3"> <div class="border-l-4 {% if not message.is_read %}border-blue-500 bg-blue-50{% else %}border-temple-red bg-gray-50{% endif %} rounded-r-lg p-4">
<div class="message-item {% if not message.is_read %}unread{% endif %}"> <div class="flex justify-between items-start mb-2">
<div class="d-flex justify-content-between align-items-start mb-2"> <div class="font-semibold text-gray-900">{{ message.subject }}</div>
<div class="fw-bold">{{ message.subject }}</div> <span class="text-sm text-gray-500">{{ message.created_at|date:"Y-m-d H:i" }}</span>
<small class="text-muted">{{ message.created_at|date:"Y-m-d H:i" }}</small>
</div> </div>
<div class="small text-muted mb-2"> <div class="text-sm text-gray-600 mb-2">
{% trans "From" %}: {{ message.sender.get_full_name }} {% trans "From" %}: {{ message.sender.get_full_name }}
</div> </div>
<div class="small">{{ message.message|truncatewords:30 }}</div> <div class="text-sm text-gray-700">{{ message.message|truncatewords:30 }}</div>
{% if not message.is_read %} {% if not message.is_read %}
<span class="badge bg-info mt-2">{% trans "New" %}</span> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mt-2">
{% trans "New" %}
</span>
{% endif %} {% endif %}
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>
{% if messages_.count > 6 %} {% if messages_.count > 6 %}
<div class="text-center mt-3"> <div class="text-center mt-4">
<a href="#" class="btn btn-outline-primary btn-sm"> <a href="#" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
{% trans "View All Messages" %} {% trans "View All Messages" %}
</a> </a>
</div> </div>
@ -422,55 +298,61 @@
</div> </div>
<!-- Cancel Assignment Modal --> <!-- Cancel Assignment Modal -->
<div class="modal fade" id="cancelAssignmentModal" tabindex="-1"> <div id="cancelAssignmentModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="modal-dialog"> <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="modal-content"> <div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('cancelAssignmentModal').classList.add('hidden')"></div>
<div class="modal-header">
<h5 class="modal-title"> <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full">
<i class="fas fa-exclamation-triangle me-2"></i> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
{% trans "Cancel Assignment" %} <div class="sm:flex sm:items-start">
</h5> <div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> <i data-lucide="alert-triangle" class="w-6 h-6 text-red-600"></i>
</div> </div>
<div class="modal-body"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<div class="alert alert-warning mb-3"> <h3 class="text-lg font-semibold text-gray-900 mb-2">
<i class="fas fa-info-circle me-2"></i> {% trans "Cancel Assignment" %}
</h3>
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-4">
<p class="text-sm text-yellow-800">
<strong>{% trans "Warning:" %}</strong> <strong>{% trans "Warning:" %}</strong>
{% trans "This action cannot be undone. The agency will no longer be able to submit applications." %} {% trans "This action cannot be undone. The agency will no longer be able to submit applications." %}
</div>
<div class="card mb-3">
<div class="card-body bg-light">
<h6 class="card-title text-primary mb-0">
<i class="fas fa-building me-2"></i>
{{ assignment.agency.name }}
</h6>
<p class="card-text text-muted mb-0">
<i class="fas fa-briefcase me-2"></i>
{{ assignment.job.title }}
</p> </p>
</div> </div>
<div class="bg-gray-50 rounded-lg p-4 mb-4">
<h6 class="text-temple-red font-semibold mb-2 flex items-center gap-2">
<i data-lucide="building" class="w-4 h-4"></i>
{{ assignment.agency.name }}
</h6>
<p class="text-gray-600 text-sm flex items-center gap-2">
<i data-lucide="briefcase" class="w-4 h-4"></i>
{{ assignment.job.title }}
</p>
</div> </div>
<form method="post" action="{% url 'agency_assignment_cancel' assignment.slug %}"> <form method="post" action="{% url 'agency_assignment_cancel' assignment.slug %}">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> <div class="mb-4">
<label for="cancel_reason" class="form-label fw-bold"> <label for="cancel_reason" class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-comment-alt me-2"></i> <i data-lucide="message-square" class="w-4 h-4 inline mr-1"></i>
{% trans "Cancellation Reason" %} {% trans "Cancellation Reason" %}
<span class="text-muted fw-normal">({% trans "Optional" %})</span> <span class="font-normal text-gray-500">({% trans "Optional" %})</span>
</label> </label>
<textarea class="form-control" id="cancel_reason" name="cancel_reason" rows="4" <textarea class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900"
id="cancel_reason" name="cancel_reason" rows="4"
placeholder="{% trans 'Enter reason for cancelling this assignment (optional)...' %}"></textarea> placeholder="{% trans 'Enter reason for cancelling this assignment (optional)...' %}"></textarea>
</div> </div>
<div class="d-flex justify-content-between align-items-center"> <div class="flex justify-end gap-3">
<a href="{% url 'agency_assignment_detail' assignment.slug %}" class="btn btn-secondary"> <button type="button"
<i class="fas fa-arrow-left me-1"></i> class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition"
onclick="document.getElementById('cancelAssignmentModal').classList.add('hidden')">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Cancel" %} {% trans "Cancel" %}
</a> </button>
<button type="submit" class="btn btn-danger"> <button type="submit"
<i class="fas fa-times-circle me-1"></i> class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
<i data-lucide="x-circle" class="w-4 h-4"></i>
{% trans "Cancel Assignment" %} {% trans "Cancel Assignment" %}
</button> </button>
</div> </div>
@ -479,107 +361,60 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<!-- Extend Deadline Modal --> <!-- Extend Deadline Modal -->
<div class="modal fade" id="extendDeadlineModal" tabindex="-1"> <div id="extendDeadlineModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
<div class="modal-dialog"> <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="modal-content"> <div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('extendDeadlineModal').classList.add('hidden')"></div>
<div class="modal-header">
<h5 class="modal-title"> <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full">
<i class="fas fa-clock me-2"></i> <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
<i data-lucide="clock" class="w-5 h-5"></i>
{% trans "Extend Assignment Deadline" %} {% trans "Extend Assignment Deadline" %}
</h5> </h3>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="post" action="{% url 'agency_assignment_extend_deadline' assignment.slug %}"> <form method="post" action="{% url 'agency_assignment_extend_deadline' assignment.slug %}">
{% csrf_token %} {% csrf_token %}
<div class="modal-body"> <div class="mb-4">
<div class="mb-3"> <label for="new_deadline" class="block text-sm font-semibold text-gray-700 mb-2">
<label for="new_deadline" class="form-label"> {% trans "New Deadline" %} <span class="text-red-600">*</span>
{% trans "New Deadline" %} <span class="text-danger">*</span>
</label> </label>
<input type="datetime-local" class="form-control" id="new_deadline" <input type="datetime-local"
name="new_deadline" required> class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900"
<small class="form-text text-muted"> id="new_deadline" name="new_deadline" required>
<p class="mt-1 text-sm text-gray-500">
{% trans "Current deadline:" %} {{ assignment.deadline_date|date:"Y-m-d H:i" }} {% trans "Current deadline:" %} {{ assignment.deadline_date|date:"Y-m-d H:i" }}
</small> </p>
</div> </div>
</div>
<div class="modal-footer"> <div class="flex justify-end gap-3">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> <button type="button"
class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition"
onclick="document.getElementById('extendDeadlineModal').classList.add('hidden')">
{% trans "Cancel" %} {% trans "Cancel" %}
</button> </button>
<button type="submit" class="btn btn-main-action"> <button type="submit"
<i class="fas fa-clock me-1"></i> {% trans "Extend Deadline" %} class="inline-flex items-center gap-2 bg-temple-red hover:bg-red-800 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
<i data-lucide="clock" class="w-4 h-4"></i>
{% trans "Extend Deadline" %}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}
{% block customJS %} {% block customJS %}
<script> <script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
// Show success message
const toast = document.createElement('div');
toast.className = 'position-fixed top-0 end-0 p-3';
toast.style.zIndex = '1050';
toast.innerHTML = `
<div class="toast show" role="alert">
<div class="toast-header">
<strong class="me-auto">{% trans "Success" %}</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
{% trans "Token copied to clipboard!" %}
</div>
</div>
`;
document.body.appendChild(toast);
setTimeout(() => {
toast.remove();
}, 3000);
});
}
function copyToClipboard(elementId) {
const element = document.getElementById(elementId);
element.select();
document.execCommand('copy');
// Show feedback
const button = element.nextElementSibling;
const originalHTML = button.innerHTML;
button.innerHTML = '<i class="fas fa-check"></i>';
button.classList.add('btn-success');
button.classList.remove('btn-outline-secondary');
setTimeout(() => {
button.innerHTML = originalHTML;
button.classList.remove('btn-success');
button.classList.add('btn-outline-secondary');
}, 2000);
}
function confirmDeactivate() {
if (confirm('{% trans "Are you sure you want to deactivate this access link? Agencies will no longer be able to use it." %}')) {
// Submit form to deactivate
window.location.href = '';
}
}
function confirmReactivate() {
if (confirm('{% trans "Are you sure you want to reactivate this access link?" %}')) {
// Submit form to reactivate
window.location.href = '';
}
}
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
lucide.createIcons();
// Set minimum datetime for new deadline // Set minimum datetime for new deadline
const deadlineInput = document.getElementById('new_deadline'); const deadlineInput = document.getElementById('new_deadline');
if (deadlineInput) { if (deadlineInput) {

File diff suppressed because it is too large Load Diff

View File

@ -1,431 +1,385 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static i18n widget_tweaks %} {% load static i18n %}
{% block title %}{{ title }} - {{ block.super }}{% endblock %} {% block title %}{{ title }} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* UI Variables for the KAAT-S Theme */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-gray-light: #f8f9fa;
}
/* Form Container Styling */
.form-container {
max-width: 800px;
margin: 0 auto;
}
/* Card Styling */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 1.5rem;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary Button Style */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Form Field Styling */
.form-control:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
}
.form-select:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
}
/* Breadcrumb Styling */
.breadcrumb {
background-color: transparent;
padding: 0;
margin-bottom: 1rem;
}
.breadcrumb-item + .breadcrumb-item::before {
content: ">";
color: var(--kaauh-teal);
}
/* Alert Styling */
.alert {
border-radius: 0.5rem;
border: none;
}
/* Loading State */
.btn.loading {
position: relative;
pointer-events: none;
opacity: 0.8;
}
.btn.loading::after {
content: "";
position: absolute;
width: 16px;
height: 16px;
margin: auto;
border: 2px solid transparent;
border-top-color: #ffffff;
border-radius: 50%;
animation: spin 1s linear infinite;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Current Profile Section */
.current-profile {
background-color: var(--kaauh-gray-light);
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1rem;
}
.current-profile h6 {
color: var(--kaauh-teal-dark);
font-weight: 600;
margin-bottom: 0.75rem;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="space-y-6">
<div class="form-container">
<!-- Breadcrumb Navigation --> <!-- Header Card with Gradient Background -->
<nav aria-label="breadcrumb"> <div class="bg-gradient-to-r from-temple-red to-[#7a1a29] rounded-2xl shadow-lg p-6 md:p-8 text-white">
<ol class="breadcrumb"> <div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
<li class="breadcrumb-item"> <div class="flex-1">
<a href="{% url 'agency_list' %}" class="text-decoration-none text-secondary"> <h1 class="text-2xl md:text-3xl font-bold mb-2 flex items-center gap-3">
<i class="fas fa-building me-1"></i> {% trans "Agencies" %} <i data-lucide="building" class="w-8 h-8"></i>
</a> {{ title }}
</li> </h1>
<p class="text-white/80 text-sm md:text-base">
{% if agency %} {% if agency %}
<li class="breadcrumb-item"> {% trans "Update agency information" %}
<a href="{% url 'agency_detail' agency.slug %}" class="text-decoration-none text-secondary">
{{ agency.name }}
</a>
</li>
<li class="breadcrumb-item active" aria-current="page"
style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;">{% trans "Update" %}</li>
{% else %} {% else %}
<li class="breadcrumb-item active" aria-current="page" {% trans "Enter details to create a new agency." %}
style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;">{% trans "Create" %}</li>
{% endif %} {% endif %}
</ol> </p>
</nav> </div>
<div class="flex flex-wrap gap-2">
{% if agency %}
<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">
<i data-lucide="eye" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "View Details" %}</span>
</a>
<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">
<i data-lucide="trash-2" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Delete" %}</span>
</a>
{% endif %}
<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">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Back to List" %}</span>
</a>
</div>
</div>
</div>
<!-- Header --> {% if agency %}
<div class="d-flex justify-content-between align-items-center mb-4"> <!-- Current Agency Info Card -->
<h6 style="color: var(--kaauh-teal-dark); font-weight: 700;"> <div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
<i class="fas fa-building me-2"></i> {{ title }} <div class="bg-temple-cream rounded-lg p-4 border-l-4 border-temple-red">
<h6 class="text-temple-dark font-bold mb-3 flex items-center gap-2">
<i data-lucide="info" class="w-4 h-4"></i>
{% trans "Currently Editing" %}
</h6> </h6>
<div class="d-flex gap-2"> <div class="space-y-2">
{% if agency %} <h5 class="text-lg font-semibold text-gray-900">{{ agency.name }}</h5>
<a href="{% url 'agency_detail' agency.slug %}" class="btn btn-outline-secondary">
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
</a>
<a href="{% url 'agency_delete' agency.slug %}" class="btn btn-danger">
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
</a>
{% endif %}
<a href="{% url 'agency_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
</a>
</div>
</div>
{% if agency %}
<!-- Current Agency Info -->
<div class="card shadow-sm mb-4">
<div class="card-body">
<div class="current-profile">
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
<div class="d-flex align-items-center">
<div>
<h5 class="mb-1">{{ agency.name }}</h5>
{% if agency.contact_person %} {% if agency.contact_person %}
<p class="text-muted mb-0">{% trans "Contact" %}: {{ agency.contact_person }}</p> <p class="text-gray-600 text-sm">{% trans "Contact" %}: {{ agency.contact_person }}</p>
{% endif %} {% endif %}
{% if agency.email %} {% if agency.email %}
<p class="text-muted mb-0">{{ agency.email }}</p> <p class="text-gray-600 text-sm">{{ agency.email }}</p>
{% endif %} {% endif %}
<small class="text-muted"> <p class="text-gray-500 text-xs">
{% trans "Created" %}: {{ agency.created_at|date:"d M Y" }} • {% trans "Created" %}: {{ agency.created_at|date:"d M Y" }} •
{% trans "Last Updated" %}: {{ agency.updated_at|date:"d M Y" }} {% trans "Last Updated" %}: {{ agency.updated_at|date:"d M Y" }}
</small> </p>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
<!-- Form Card --> <!-- Form Card -->
<div class="card shadow-sm"> <div class="bg-white rounded-xl shadow-sm border border-gray-200">
<div class="card-body p-4"> <div class="border-b border-gray-200 px-6 py-4 bg-gray-50 rounded-t-xl">
<h2 class="text-lg font-bold text-temple-red flex items-center gap-2">
<i data-lucide="file-text" class="w-5 h-5"></i>
{% trans "Agency Information" %}
</h2>
</div>
<div class="p-6 md:p-8">
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="alert alert-danger" role="alert"> <div class="bg-red-50 border border-red-200 rounded-xl p-4 mb-6" role="alert">
<h5 class="alert-heading"> <div class="flex items-start gap-3">
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %} <i data-lucide="alert-circle" class="w-5 h-5 text-red-600 shrink-0 mt-0.5"></i>
</h5> <div>
<h5 class="font-bold text-red-800 mb-1">{% trans "Error" %}</h5>
{% for error in form.non_field_errors %} {% for error in form.non_field_errors %}
<p class="mb-0">{{ error }}</p> <p class="text-red-700 text-sm">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
</div>
</div>
{% endif %} {% endif %}
{% if messages %} <form method="post" novalidate id="agency-form" class="space-y-6">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
</div>
{% endfor %}
{% endif %}
<form method="post" novalidate id="agency-form">
{% csrf_token %} {% csrf_token %}
<!-- Two Column Form Layout -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Name --> <!-- Name -->
<div class="mb-3"> <div class="space-y-2">
<label for="{{ form.name.id_for_label }}" class="form-label"> <label for="{{ form.name.id_for_label }}" class="block text-sm font-semibold text-gray-700">
{{ form.name.label }} <span class="text-danger">*</span> {{ form.name.label }} <span class="text-red-600">*</span>
</label> </label>
{{ form.name|add_class:"form-control" }} <input type="text"
name="name"
id="{{ form.name.id_for_label }}"
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"
placeholder="{% trans 'Agency Name' %}"
{% if form.name.field.required %}required{% endif %}>
{% if form.name.errors %} {% if form.name.errors %}
{% for error in form.name.errors %} {% for error in form.name.errors %}
<div class="invalid-feedback d-block">{{ error }}</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 %} {% endfor %}
{% endif %} {% endif %}
{% if form.name.help_text %} {% if form.name.help_text %}
<div class="form-text">{{ form.name.help_text }}</div> <p class="text-gray-500 text-xs">{{ form.name.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<!-- Contact Person and Phone --> <!-- Contact Person -->
<div class="row"> <div class="space-y-2">
<div class="col-md-6 mb-3"> <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="form-label">
{{ form.contact_person.label }} {{ form.contact_person.label }}
</label> </label>
{{ form.contact_person|add_class:"form-control" }} <input type="text"
name="contact_person"
id="{{ form.contact_person.id_for_label }}"
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"
placeholder="{% trans 'Contact Person' %}">
{% if form.contact_person.errors %} {% if form.contact_person.errors %}
{% for error in form.contact_person.errors %} {% for error in form.contact_person.errors %}
<div class="invalid-feedback d-block">{{ error }}</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 %} {% endfor %}
{% endif %} {% endif %}
{% if form.contact_person.help_text %} {% if form.contact_person.help_text %}
<div class="form-text">{{ form.contact_person.help_text }}</div> <p class="text-gray-500 text-xs">{{ form.contact_person.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<div class="col-md-6 mb-3"> <!-- Phone -->
<label for="{{ form.phone.id_for_label }}" class="form-label"> <div class="space-y-2">
<label for="{{ form.phone.id_for_label }}" class="block text-sm font-semibold text-gray-700">
{{ form.phone.label }} {{ form.phone.label }}
</label> </label>
{{ form.phone|add_class:"form-control" }} <input type="tel"
name="phone"
id="{{ form.phone.id_for_label }}"
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"
placeholder="{% trans 'Phone Number' %}">
{% if form.phone.errors %} {% if form.phone.errors %}
{% for error in form.phone.errors %} {% for error in form.phone.errors %}
<div class="invalid-feedback d-block">{{ error }}</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 %} {% endfor %}
{% endif %} {% endif %}
{% if form.phone.help_text %} {% if form.phone.help_text %}
<div class="form-text">{{ form.phone.help_text }}</div> <p class="text-gray-500 text-xs">{{ form.phone.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
</div>
<!-- Email and Website --> <!-- Email -->
<div class="row"> <div class="space-y-2">
<div class="col-md-6 mb-3"> <label for="{{ form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700">
<label for="{{ form.email.id_for_label }}" class="form-label"> {{ form.email.label }} <span class="text-red-600">*</span>
{{ form.email.label }}<span class="text-danger">*</span>
</label> </label>
{{ form.email|add_class:"form-control" }} <input type="email"
name="email"
id="{{ form.email.id_for_label }}"
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"
placeholder="{% trans 'Email Address' %}"
{% if form.email.field.required %}required{% endif %}>
{% if form.email.errors %} {% if form.email.errors %}
{% for error in form.email.errors %} {% for error in form.email.errors %}
<div class="invalid-feedback d-block">{{ error }}</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 %} {% endfor %}
{% endif %} {% endif %}
{% if form.email.help_text %} {% if form.email.help_text %}
<div class="form-text">{{ form.email.help_text }}</div> <p class="text-gray-500 text-xs">{{ form.email.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<div class="col-md-6 mb-3"> <!-- Website -->
<label for="{{ form.website.id_for_label }}" class="form-label"> <div class="space-y-2">
<label for="{{ form.website.id_for_label }}" class="block text-sm font-semibold text-gray-700">
{{ form.website.label }} {{ form.website.label }}
</label> </label>
{{ form.website|add_class:"form-control" }} <input type="url"
name="website"
id="{{ form.website.id_for_label }}"
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"
placeholder="{% trans 'Website URL' %}">
{% if form.website.errors %} {% if form.website.errors %}
{% for error in form.website.errors %} {% for error in form.website.errors %}
<div class="invalid-feedback d-block">{{ error }}</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 %} {% endfor %}
{% endif %} {% endif %}
{% if form.website.help_text %} {% if form.website.help_text %}
<div class="form-text">{{ form.website.help_text }}</div> <p class="text-gray-500 text-xs">{{ form.website.help_text }}</p>
{% endif %}
</div>
</div>
<!-- Address -->
<div class="mb-3">
<label for="{{ form.address.id_for_label }}" class="form-label">
{{ form.address.label }}
</label>
{{ form.address|add_class:"form-control" }}
{% if form.address.errors %}
{% for error in form.address.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
{% endif %}
{% if form.address.help_text %}
<div class="form-text">{{ form.address.help_text }}</div>
{% endif %} {% endif %}
</div> </div>
<!-- Country and City --> <!-- Country -->
<div class="row"> <div class="space-y-2">
<div class="col-md-6 mb-3"> <label for="{{ form.country.id_for_label }}" class="block text-sm font-semibold text-gray-700">
<label for="{{ form.country.id_for_label }}" class="form-label">
{{ form.country.label }} {{ form.country.label }}
</label> </label>
{{ form.country|add_class:"form-control" }} <input type="text"
name="country"
id="{{ form.country.id_for_label }}"
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"
placeholder="{% trans 'Country' %}">
{% if form.country.errors %} {% if form.country.errors %}
{% for error in form.country.errors %} {% for error in form.country.errors %}
<div class="invalid-feedback d-block">{{ error }}</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 %} {% endfor %}
{% endif %} {% endif %}
{% if form.country.help_text %} {% if form.country.help_text %}
<div class="form-text">{{ form.country.help_text }}</div> <p class="text-gray-500 text-xs">{{ form.country.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<div class="col-md-6 mb-3"> <!-- City -->
<label for="{{ form.city.id_for_label }}" class="form-label"> <div class="space-y-2">
<label for="{{ form.city.id_for_label }}" class="block text-sm font-semibold text-gray-700">
{{ form.city.label }} {{ form.city.label }}
</label> </label>
{{ form.city|add_class:"form-control" }} <input type="text"
name="city"
id="{{ form.city.id_for_label }}"
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"
placeholder="{% trans 'City' %}">
{% if form.city.errors %} {% if form.city.errors %}
{% for error in form.city.errors %} {% for error in form.city.errors %}
<div class="invalid-feedback d-block">{{ error }}</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 %} {% endfor %}
{% endif %} {% endif %}
{% if form.city.help_text %} {% if form.city.help_text %}
<div class="form-text">{{ form.city.help_text }}</div> <p class="text-gray-500 text-xs">{{ form.city.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- Description --> <!-- Address (Full Width) -->
<div class="mb-4"> <div class="space-y-2">
<label for="{{ form.description.id_for_label }}" class="form-label"> <label for="{{ form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700">
{{ form.address.label }}
</label>
<textarea
name="address"
id="{{ form.address.id_for_label }}"
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"
placeholder="{% trans 'Street Address' %}">{{ form.address.value|default:'' }}</textarea>
{% if form.address.errors %}
{% for error in form.address.errors %}
<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 %}
{% if form.address.help_text %}
<p class="text-gray-500 text-xs">{{ form.address.help_text }}</p>
{% endif %}
</div>
<!-- Description (Full Width) -->
<div class="space-y-2">
<label for="{{ form.description.id_for_label }}" class="block text-sm font-semibold text-gray-700">
{{ form.description.label }} {{ form.description.label }}
</label> </label>
{{ form.description|add_class:"form-control" }} <textarea
name="description"
id="{{ form.description.id_for_label }}"
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"
placeholder="{% trans 'Agency Description' %}">{{ form.description.value|default:'' }}</textarea>
{% if form.description.errors %} {% if form.description.errors %}
{% for error in form.description.errors %} {% for error in form.description.errors %}
<div class="invalid-feedback d-block">{{ error }}</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 %} {% endfor %}
{% endif %} {% endif %}
{% if form.description.help_text %} {% if form.description.help_text %}
<div class="form-text">{{ form.description.help_text }}</div> <p class="text-gray-500 text-xs">{{ form.description.help_text }}</p>
{% endif %} {% endif %}
</div> </div>
<div class="d-flex gap-2"> <!-- Submit Button -->
<button form="agency-form" type="submit" class="btn btn-main-action"> <div class="pt-4 border-t border-gray-200">
<i class="fas fa-save me-1"></i> {{ button_text }} <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">
<i data-lucide="save" class="w-4 h-4"></i>
{{ button_text }}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %}
{% block customJS %}
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Reinitialize Lucide icons for dynamically added content
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 submitBtn = form.querySelector('button[type="submit"]');
submitBtn.classList.add('loading'); submitBtn.classList.add('opacity-75', 'cursor-not-allowed');
submitBtn.disabled = true; submitBtn.disabled = true;
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 // Basic validation
const name = document.getElementById('id_name'); const name = document.getElementById('{{ form.name.id_for_label }}');
if (name && !name.value.trim()) { if (name && !name.value.trim()) {
e.preventDefault(); e.preventDefault();
submitBtn.classList.remove('loading'); submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`;
lucide.createIcons();
alert('{% trans "Agency name is required." %}'); alert('{% trans "Agency name is required." %}');
return; return;
} }
const email = document.getElementById('id_email'); const email = document.getElementById('{{ form.email.id_for_label }}');
if (email && email.value.trim() && !isValidEmail(email.value.trim())) { if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
e.preventDefault(); e.preventDefault();
submitBtn.classList.remove('loading'); submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
submitBtn.disabled = false; 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." %}'); alert('{% trans "Please enter a valid email address." %}');
return; return;
} }
const website = document.getElementById('id_website'); const website = document.getElementById('{{ form.website.id_for_label }}');
if (website && website.value.trim() && !isValidURL(website.value.trim())) { if (website && website.value.trim() && !isValidURL(website.value.trim())) {
e.preventDefault(); e.preventDefault();
submitBtn.classList.remove('loading'); submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
submitBtn.disabled = false; 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." %}'); alert('{% trans "Please enter a valid website URL." %}');
return; return;
} }

View File

@ -3,269 +3,141 @@
{% block title %}{{ assignment.job.title }} - {{ assignment.agency.name }} - {% trans "Agency Portal" %}{% endblock %} {% block title %}{{ assignment.job.title }} - {{ assignment.agency.name }} - {% trans "Agency Portal" %}{% endblock %}
{% block customCSS %}
<style>
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745;
--kaauh-info: #17a2b8;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.status-badge {
font-size: 0.75rem;
padding: 0.3em 0.7em;
border-radius: 0.35rem;
font-weight: 700;
}
.status-ACTIVE { background-color: var(--kaauh-teal-dark); color: white; }
.status-EXPIRED { background-color: var(--kaauh-teal-dark); color: white; }
.status-COMPLETED { background-color: var(--kaauh-teal-dark); color: white; }
.status-CANCELLED { background-color: var(--kaauh-teal-dark); color: white; }
.progress-ring {
width: 120px;
height: 120px;
position: relative;
}
.progress-ring-circle {
transition: stroke-dashoffset 0.35s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
.progress-ring-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 1.5rem;
font-weight: 700;
color: var(--kaauh-teal-dark);
}
.candidate-item {
border-left: 4px solid var(--kaauh-teal);
background-color: #f8f9fa;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 0 0.5rem 0.5rem 0;
transition: all 0.2s ease;
}
.candidate-item:hover {
background-color: #e9ecef;
transform: translateX(2px);
}
.message-item {
border-left: 4px solid var(--kaauh-teal);
background-color: #f8f9fa;
padding: 1rem;
margin-bottom: 1rem;
border-radius: 0 0.5rem 0.5rem 0;
}
.message-item.unread {
border-left-color: var(--kaauh-info);
background-color: #e7f3ff;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container mx-auto px-4 py-8">
<!-- Header --> <!-- 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">
<div> <div>
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
<i class="fas fa-briefcase me-2"></i> <div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="briefcase" class="w-8 h-8 text-temple-red"></i>
</div>
{{ assignment.job.title }} {{ assignment.job.title }}
</h1> </h1>
<p class="text-muted mb-0"> <p class="text-gray-600">{% trans "Assignment Details" %} - {{ assignment.agency.name }}</p>
{% trans "Assignment Details" %} - {{ assignment.agency.name }}
</p>
</div> </div>
<div> <div class="flex gap-2">
<a href="{% url 'agency_portal_dashboard' %}" class="btn btn-outline-secondary btn-sm me-2"> <a href="{% url 'agency_portal_dashboard' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl transition">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Dashboard" %} <i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Dashboard" %}
</a> </a>
<a href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="btn btn-sm btn-main-action {% if assignment.is_full %}disabled{% endif %}" > <a href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition shadow-sm hover:shadow-md {% if assignment.is_full %}opacity-50 cursor-not-allowed{% endif %}">
<i class="fas fa-user-plus me-1"></i> {% trans "Submit New application" %} <i data-lucide="user-plus" class="w-4 h-4"></i> {% trans "Submit New application" %}
</a> </a>
{% comment %} <a href="#" class="btn btn-outline-info">
<i class="fas fa-envelope me-1"></i> {% trans "Messages" %}
{% if total_unread_messages > 0 %}
<span class="badge bg-danger ms-1">{{ total_unread_messages }}</span>
{% endif %}
</a> {% endcomment %}
</div> </div>
</div> </div>
<div class="row"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Assignment Overview --> <!-- Main Content -->
<div class="col-lg-8"> <div class="lg:col-span-2 space-y-6">
<!-- Assignment Details Card --> <!-- Assignment Details Card -->
<div class="kaauh-card p-4 mb-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);"> <div class="p-6">
<i class="fas fa-info-circle me-2"></i> <h5 class="text-xl font-bold text-gray-900 mb-6 flex items-center gap-2">
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
<i data-lucide="info" class="w-5 h-5 text-temple-red"></i>
</div>
{% trans "Assignment Details" %} {% trans "Assignment Details" %}
</h5> </h5>
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="col-md-6">
<div class="mb-3">
<label class="text-muted small">{% trans "Job Title" %}</label>
<div class="fw-bold">{{ assignment.job.title }}</div>
<div class="text-muted small">{{ assignment.job.department }}</div>
</div>
<div class="mb-3">
<label class="text-muted small">{% trans "Status" %}</label>
<div> <div>
<span class="status-badge status-{{ assignment.status }}" > <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Job Title" %}</label>
<div class="font-bold text-gray-900">{{ assignment.job.title }}</div>
<div class="text-sm text-gray-600">{{ assignment.job.department }}</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Status" %}</label>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
{{ assignment.get_status_display }} {{ assignment.get_status_display }}
</span> </span>
</div> </div>
</div> <div>
</div> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Deadline" %}</label>
<div class="col-md-6"> <div class="{% if assignment.is_expired %}text-red-600{% else %}text-gray-700{% endif %}">
<div class="mb-3"> <i data-lucide="calendar" class="w-4 h-4 inline mr-1"></i>
<label class="text-muted small">{% trans "Deadline" %}</label>
<div class="{% if assignment.is_expired %}text-danger{% else %}text-muted{% endif %}">
<i class="fas fa-calendar-alt me-1"></i>
{{ assignment.deadline_date|date:"Y-m-d H:i" }} {{ assignment.deadline_date|date:"Y-m-d H:i" }}
</div> </div>
{% if assignment.is_expired %} {% if assignment.is_expired %}
<small class="text-danger"> <div class="text-sm text-red-600 mt-1">
<i class="fas fa-exclamation-triangle me-1"></i>{% trans "Expired" %} <i data-lucide="alert-triangle" class="w-3 h-3 inline mr-1"></i>{% trans "Expired" %}
</small> </div>
{% else %} {% else %}
<small class="text-primary-theme"> <div class="text-sm text-blue-600 mt-1">
<i class="fas fa-clock me-1"></i>{{ assignment.days_remaining }} {% trans "days remaining" %} <i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>{{ assignment.days_remaining }} {% trans "days remaining" %}
</small> </div>
{% endif %} {% endif %}
</div> </div>
<div class="mb-3"> <div>
<label class="text-muted small">{% trans "Maximum applications" %}</label> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Maximum applications" %}</label>
<div class="fw-bold">{{max_applications }} {% trans "applications" %}</div> <div class="font-bold text-gray-900">{{max_applications }} {% trans "applications" %}</div>
</div>
</div> </div>
</div> </div>
{% if assignment.job.description %} {% if assignment.job.description %}
<div class="mt-3 pt-3 border-top"> <div class="mt-6 pt-6 border-t border-gray-200">
<label class="text-muted small">{% trans "Job Description " %}</label> <label class="block text-sm font-semibold text-gray-500 mb-2">{% trans "Job Description" %}</label>
<div class="text-muted"> <div class="text-gray-700">{{ assignment.job.description|safe|truncatewords:50 }}</div>
{{ assignment.job.description|safe|truncatewords:50 }}</div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<!-- Quick Actions Card -->
{% comment %} <div class="kaauh-card p-4 mb-4">
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-bolt me-2"></i>
{% trans "Quick Actions" %}
</h5>
<div class="d-grid gap-2">
{% if assignment.can_submit %}
<a href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="btn btn-main-action">
<i class="fas fa-user-plus me-1"></i> {% trans "Submit New application" %}
</a>
{% else %}
<button class="btn btn-outline-secondary" disabled>
<i class="fas fa-user-plus me-1"></i> {% trans "Cannot Submit applications" %}
</button>
<div class="alert alert-warning mt-2">
<i class="fas fa-exclamation-triangle me-2"></i>
{% if assignment.is_expired %}
{% trans "This assignment has expired. Submissions are no longer accepted." %}
{% elif assignment.is_full %}
{% trans "Maximum application limit reached for this assignment." %}
{% else %}
{% trans "This assignment is not currently active." %}
{% endif %}
</div>
{% endif %}
<a href="{% url 'agency_portal_dashboard' %}" class="btn btn-outline-secondary">
<i class="fas fa-dashboard me-1"></i> {% trans "Dashboard" %}
</a>
<a href="#" class="btn btn-outline-info">
<i class="fas fa-comments me-1"></i> {% trans "All Messages" %}
</a>
</div>
</div> </div>
<!-- Submitted applications --> {% endcomment %} <!-- Submitted applications -->
<div class="kaauh-card p-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="p-6">
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);"> <div class="flex justify-between items-center mb-6">
<i class="fas fa-users me-2"></i> <h5 class="text-xl font-bold text-gray-900 flex items-center gap-2">
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
<i data-lucide="users" class="w-5 h-5 text-temple-red"></i>
</div>
{% trans "Submitted applications" %} ({{ total_applications }}) {% trans "Submitted applications" %} ({{ total_applications }})
</h5> </h5>
<span class="badge bg-primary-theme">{{ total_applications }}/{{ max_applications }}</span> <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-temple-red/10 text-temple-red">
{{ total_applications }}/{{ max_applications }}
</span>
</div> </div>
{% if page_obj %} {% if page_obj %}
<div class="table-responsive"> <div class="overflow-x-auto">
<table class="table table-hover"> <table class="w-full">
<thead> <thead>
<tr> <tr class="border-b border-gray-200">
<th>{% trans "Name" %}</th> <th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Name" %}</th>
<th>{% trans "Contact" %}</th> <th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Contact" %}</th>
<th>{% trans "Stage" %}</th> <th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Stage" %}</th>
<th>{% trans "Submitted" %}</th> <th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Submitted" %}</th>
<th>{% trans "Actions" %}</th> <th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Actions" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for application in page_obj %} {% for application in page_obj %}
<tr> <tr class="border-b border-gray-100 hover:bg-gray-50 transition">
<td> <td class="py-3 px-4">
<div class="fw-bold">{{ application.name }}</div> <div class="font-semibold text-gray-900">{{ application.name }}</div>
</td> </td>
<td> <td class="py-3 px-4">
<div class="small"> <div class="text-sm text-gray-600">
<div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div> <div class="flex items-center gap-1">
<div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div> <i data-lucide="mail" class="w-3 h-3"></i> {{ application.email }}
</div>
<div class="flex items-center gap-1">
<i data-lucide="phone" class="w-3 h-3"></i> {{ application.phone }}
</div>
</div> </div>
</td> </td>
<td> <td class="py-3 px-4">
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-temple-red/10 text-temple-red">
{{ application.get_stage_display }}
</span>
</td> </td>
<td> <td class="py-3 px-4">
<div class="small text-muted"> <div class="text-sm text-gray-500">
{{ application.created_at|date:"Y-m-d H:i" }} {{ application.created_at|date:"Y-m-d H:i" }}
</div> </div>
</td> </td>
<td> <td class="py-3 px-4">
<a href="{% url 'applicant_application_detail' application.slug %}" class="btn btn-sm btn-outline-primary" title="{% trans 'View Profile' %}"> <a href="{% url 'applicant_application_detail' application.slug %}" class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition" title="{% trans 'View Profile' %}">
<i class="fas fa-eye"></i> <i data-lucide="eye" class="w-4 h-4"></i>
</a> </a>
</td> </td>
</tr> </tr>
@ -276,127 +148,111 @@
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<nav aria-label="Candidate pagination"> <div class="flex justify-center items-center gap-2 mt-6">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item"> <a href="?page={{ page_obj.previous_page_number }}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}"> <i data-lucide="chevron-left" class="w-4 h-4"></i>
<i class="fas fa-chevron-left"></i>
</a> </a>
</li>
{% endif %} {% endif %}
{% for num in page_obj.paginator.page_range %} {% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %} {% if page_obj.number == num %}
<li class="page-item active"> <span class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-temple-red text-white font-semibold">{{ num }}</span>
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %} {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item"> <a href="?page={{ num }}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">{{ num }}</a>
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
</li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="page-item"> <a href="?page={{ page_obj.next_page_number }}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
<a class="page-link" href="?page={{ page_obj.next_page_number }}"> <i data-lucide="chevron-right" class="w-4 h-4"></i>
<i class="fas fa-chevron-right"></i>
</a> </a>
</li>
{% endif %} {% endif %}
</ul> </div>
</nav>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="text-center py-4"> <div class="text-center py-8">
<i class="fas fa-users fa-2x text-muted mb-3"></i> <div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<h6 class="text-muted">{% trans "No applications submitted yet" %}</h6> <i data-lucide="users" class="w-8 h-8 text-gray-400"></i>
<p class="text-muted small"> </div>
{% trans "Submit applications using the form above to get started." %} <h6 class="text-gray-600 mb-2">{% trans "No applications submitted yet" %}</h6>
<p class="text-gray-500 text-sm">
{% trans "Submit applications using form above to get started." %}
</p> </p>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- Sidebar -->
</div> </div>
<div class="col-lg-4 col-md-12">
<!-- Progress Card -->
<div class="col kaauh-card p-4 mb-4">
<h5 class="mb-4 text-center" style="color: var(--kaauh-teal-dark);">
{% trans "Submission Progress" %}
</h5>
<div class="text-center mb-3"> <!-- Sidebar -->
<div class="progress-ring"> <div class="lg:col-span-1 space-y-6">
<svg width="120" height="120"> <!-- Progress Card -->
<circle class="progress-ring-circle" <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
stroke="#e9ecef" <div class="p-6">
stroke-width="8" <h5 class="text-xl font-bold text-gray-900 mb-4 text-center">{% trans "Submission Progress" %}</h5>
fill="transparent"
r="52" <div class="text-center mb-4">
cx="60" <div class="relative w-32 h-32 mx-auto">
cy="60"/> <svg width="128" height="128" viewBox="0 0 128 128" class="transform -rotate-90">
<circle class="progress-ring-circle" <circle cx="64" cy="64" r="56" stroke="#e5e7eb" stroke-width="8" fill="none"/>
stroke="var(--kaauh-teal)" <circle cx="64" cy="64" r="56" stroke="#9d2235" stroke-width="8" fill="none"
stroke-width="8" style="stroke-dasharray: 351.86; stroke-dashoffset: {{ stroke_dashoffset }};"/>
fill="transparent"
r="52"
cx="60"
cy="60"
style="stroke-dasharray: 326.73; stroke-dashoffset: {{ stroke_dashoffset }};"/>
</svg> </svg>
<div class="progress-ring-text"> <div class="absolute inset-0 flex items-center justify-center">
{% widthratio total_applications assignment.max_candidates 100 as progress %} {% widthratio total_applications assignment.max_candidates 100 as progress %}
{{ progress|floatformat:0 }}% <span class="text-3xl font-bold text-gray-900">{{ progress|floatformat:0 }}%</span>
</div> </div>
</div> </div>
</div> </div>
<div class="text-center mb-4">
<div class="text-4xl font-bold text-gray-900 mb-1">{{ total_applications }}</div>
<div class="text-gray-600">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
</div>
{% widthratio total_applications assignment.max_candidates 100 as progress %}
<div class="w-full bg-gray-200 rounded-full h-2 mb-4">
<div class="h-2 rounded-full bg-temple-red" style="width: {{ progress }}%"></div>
</div>
<div class="text-center"> <div class="text-center">
<div class="h4 mb-1">{{ total_applications }}</div>
<div class="text-muted">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
</div>
<div class="progress mt-3" style="height: 8px;">
{% widthratio total_applications assignment.max_candidates 100 as progress %}
<div class="progress-bar bg-primary-theme" style="width: {{ progress }}%"></div>
</div>
<div class="mt-3 text-center">
{% if assignment.can_submit %} {% if assignment.can_submit %}
<span class="badge bg-primary-theme">{% trans "Can Submit" %}</span> <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">{% trans "Can Submit" %}</span>
{% else %} {% else %}
<span class="badge bg-danger">{% trans "Cannot Submit" %}</span> <span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 text-red-800">{% trans "Cannot Submit" %}</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
<!-- Assignment Info Card --> <!-- Assignment Info Card -->
<div class="col kaauh-card p-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);"> <div class="p-6">
<i class="fas fa-info me-2"></i> <h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
<i data-lucide="info" class="w-5 h-5 text-temple-red"></i>
</div>
{% trans "Assignment Info" %} {% trans "Assignment Info" %}
</h5> </h5>
<div class="mb-3"> <div class="space-y-4">
<label class="text-muted small">{% trans "Assigned Date" %}</label> <div>
<div class="fw-bold">{{ assignment.assigned_date|date:"Y-m-d" }}</div> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Assigned Date" %}</label>
<div class="font-bold text-gray-900">{{ assignment.assigned_date|date:"Y-m-d" }}</div>
</div> </div>
<div class="mb-3"> <div>
<label class="text-muted small">{% trans "Days Remaining" %}</label> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Days Remaining" %}</label>
<div class="fw-bold {% if assignment.days_remaining <= 3 %}text-danger{% endif %}"> <div class="font-bold {% if assignment.days_remaining <= 3 %}text-red-600{% endif %} text-gray-900">
{{ assignment.days_remaining }} {% trans "days" %} {{ assignment.days_remaining }} {% trans "days" %}
</div> </div>
</div> </div>
<div class="mb-3"> <div>
<label class="text-muted small">{% trans "Submission Rate" %}</label> <label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Submission Rate" %}</label>
<div class="fw-bold">
{% widthratio total_applications assignment.max_candidates 100 as progress %} {% widthratio total_applications assignment.max_candidates 100 as progress %}
{{ progress|floatformat:1 }}% <div class="font-bold text-gray-900">{{ progress|floatformat:1 }}%</div>
</div> </div>
</div> </div>
</div> </div>
@ -404,293 +260,50 @@
<!-- Recent Messages Section --> <!-- Recent Messages Section -->
{% if message_page_obj %} {% if message_page_obj %}
<div class="kaauh-card p-4 mt-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);"> <div class="p-6">
<i class="fas fa-comments me-2"></i> <h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
<i data-lucide="message-circle" class="w-5 h-5 text-temple-red"></i>
</div>
{% trans "Recent Messages" %} {% trans "Recent Messages" %}
</h5> </h5>
<div class="row"> <div class="space-y-3">
{% for message in message_page_obj|slice:":6" %} {% for message in message_page_obj|slice:":6" %}
<div class="col-lg-6 mb-3"> <div class="border-l-4 border-temple-red bg-gray-50 rounded-r-lg p-4 {% if not message.is_read %}bg-blue-50 border-blue-500{% endif %}">
<div class="message-item {% if not message.is_read %}unread{% endif %}"> <div class="flex justify-between items-start mb-2">
<div class="d-flex justify-content-between align-items-start mb-2"> <div class="font-semibold text-gray-900">{{ message.subject }}</div>
<div class="fw-bold">{{ message.subject }}</div> <div class="text-xs text-gray-500">{{ message.created_at|date:"Y-m-d H:i" }}</div>
<small class="text-muted">{{ message.created_at|date:"Y-m-d H:i" }}</small>
</div> </div>
<div class="small text-muted mb-2"> <div class="text-sm text-gray-600 mb-2">
{% trans "From" %}: {{ message.sender.get_full_name }} {% trans "From" %}: {{ message.sender.get_full_name }}
</div> </div>
<div class="small">{{ message.message|truncatewords:30 }}</div> <div class="text-sm text-gray-700">{{ message.message|truncatewords:30 }}</div>
{% if not message.is_read %} {% if not message.is_read %}
<span class="badge bg-info mt-2">{% trans "New" %}</span> <span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 mt-2">{% trans "New" %}</span>
{% endif %} {% endif %}
</div> </div>
</div>
{% endfor %} {% endfor %}
</div> </div>
{% if message_page_obj.count > 6 %} {% if message_page_obj.count > 6 %}
<div class="text-center mt-3"> <div class="text-center mt-4">
<a href="#" class="btn btn-outline-primary btn-sm"> <a href="#" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl transition">
{% trans "View All Messages" %} {% trans "View All Messages" %}
</a> </a>
</div> </div>
{% endif %} {% endif %}
</div> </div>
</div>
{% endif %} {% endif %}
</div> </div>
<!-- Message Modal -->
<div class="modal fade" id="messageModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-envelope me-2"></i>
{% trans "Send Message to Admin" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="post" action="#">
{% csrf_token %}
<input type="hidden" name="assignment_id" value="{{ assignment.id }}">
<div class="modal-body">
<div class="mb-3">
<label for="subject" class="form-label">
{% trans "Subject" %} <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="subject" name="subject" required>
</div>
<div class="mb-3">
<label for="priority" class="form-label">{% trans "Priority" %}</label>
<select class="form-select" id="priority" name="priority">
<option value="low">{% trans "Low" %}</option>
<option value="medium" selected>{% trans "Medium" %}</option>
<option value="high">{% trans "High" %}</option>
<option value="urgent">{% trans "Urgent" %}</option>
</select>
</div>
<div class="mb-3">
<label for="content" class="form-label">
{% trans "Message" %} <span class="text-danger">*</span>
</label>
<textarea class="form-control" id="content" name="content" rows="5" required></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">
{% trans "Cancel" %}
</button>
<button type="submit" class="btn btn-main-action">
<i class="fas fa-paper-plane me-1"></i> {% trans "Send Message" %}
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit application Modal -->
<div class="modal fade" id="editCandidateModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-edit me-2"></i>
{% trans "Edit application" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="editCandidateForm" method="post" action="{% url 'agency_portal_edit_application' 0 %}">
{% csrf_token %}
<input type="hidden" id="edit_candidate_id" name="candidate_id">
<div class="modal-body">
<div class="row">
<div class="col-md-6 mb-3">
<label for="edit_first_name" class="form-label">
{% trans "First Name" %} <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="edit_first_name" name="first_name" required>
</div>
<div class="col-md-6 mb-3">
<label for="edit_last_name" class="form-label">
{% trans "Last Name" %} <span class="text-danger">*</span>
</label>
<input type="text" class="form-control" id="edit_last_name" name="last_name" required>
</div> </div>
</div> </div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="edit_email" class="form-label">
{% trans "Email" %} <span class="text-danger">*</span>
</label>
<input type="email" class="form-control" id="edit_email" name="email" required>
</div>
<div class="col-md-6 mb-3">
<label for="edit_phone" class="form-label">
{% trans "Phone" %} <span class="text-danger">*</span>
</label>
<input type="tel" class="form-control" id="edit_phone" name="phone" required>
</div>
</div>
<div class="mb-3">
<label for="edit_address" class="form-label">
{% trans "Address" %} <span class="text-danger">*</span>
</label>
<textarea class="form-control" id="edit_address" name="address" rows="3" required></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
{% trans "Cancel" %}
</button>
<button type="submit" class="btn btn-main-action">
<i class="fas fa-save me-1"></i> {% trans "Save Changes" %}
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Delete Confirmation Modal -->
<div class="modal fade" id="deleteCandidateModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<i class="fas fa-trash me-2"></i>
{% trans "Remove application" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="deleteCandidateForm" method="post" action="{% url 'agency_portal_delete_application' 0 %}">
{% csrf_token %}
<input type="hidden" id="delete_candidate_id" name="candidate_id">
<div class="modal-body">
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
{% trans "Are you sure you want to remove this application? This action cannot be undone." %}
</div>
<p><strong>{% trans "Application:" %}</strong> <span id="delete_candidate_name"></span></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
{% trans "Cancel" %}
</button>
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash me-1"></i> {% trans "Remove Application" %}
</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block customJS %}
<script> <script>
// Edit Candidate
function editCandidate(candidateId) {
// Update form action URL with candidate ID
const editForm = document.getElementById('editCandidateForm');
editForm.action = editForm.action.replace('/0/', `/${candidateId}/`);
// Fetch candidate data and populate modal
fetch(`/api/candidate/${candidateId}/`)
.then(response => response.json())
.then(data => {
document.getElementById('edit_candidate_id').value = data.id;
document.getElementById('edit_first_name').value = data.first_name;
document.getElementById('edit_last_name').value = data.last_name;
document.getElementById('edit_email').value = data.email;
document.getElementById('edit_phone').value = data.phone;
document.getElementById('edit_address').value = data.address;
new bootstrap.Modal(document.getElementById('editCandidateModal')).show();
})
.catch(error => {
console.error('Error fetching Application:', error);
alert('{% trans "Error loading Application data. Please try again." %}');
});
}
// Delete Application
function deleteCandidate(candidateId, candidateName) {
// Update form action URL with candidate ID
const deleteForm = document.getElementById('deleteCandidateForm');
deleteForm.action = deleteForm.action.replace('/0/', `/${candidateId}/`);
document.getElementById('delete_candidate_id').value = candidateId;
document.getElementById('delete_candidate_name').textContent = candidateName;
new bootstrap.Modal(document.getElementById('deleteCandidateModal')).show();
}
// Handle form submissions
document.getElementById('editCandidateForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch(this.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('editCandidateModal')).hide();
location.reload();
} else {
alert(data.message || '{% trans "Error updating Application. Please try again." %}');
}
})
.catch(error => {
console.error('Error:', error);
alert('{% trans "Error updating Application. Please try again." %}');
});
});
document.getElementById('deleteCandidateForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch(this.action, {
method: 'POST',
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('deleteCandidateModal')).hide();
location.reload();
} else {
alert(data.message || '{% trans "Error removing Application. Please try again." %}');
}
})
.catch(error => {
console.error('Error:', error);
alert('{% trans "Error removing Application. Please try again." %}');
});
});
// Auto-focus on first input in submission form
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const firstNameField = document.getElementById('first_name'); lucide.createIcons();
if (firstNameField) {
firstNameField.focus();
}
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,219 +1,204 @@
{% extends 'portal_base.html' %} {% extends 'portal_base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% trans "Agency Dashboard" %} -{% trans "Aagency Portal" %}{% endblock %} {% block title %}{% trans "Agency Dashboard" %} - {% trans "Agency Portal" %}{% endblock %}
{% block customCSS %}
<style>
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745;
--kaauh-info: #17a2b8;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
</style>
{% endblock%}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="space-y-6">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="px-2 py-2"> <!-- Header Section -->
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white rounded-2xl shadow-lg p-6 md:p-8">
<i class="fas fa-tachometer-alt me-2"></i> <div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
{% trans "Agency Dashboard" %} <div>
</h1> <div class="flex items-center gap-3 mb-2">
<p class="text-muted mb-0"> <div class="w-14 h-14 rounded-xl bg-white/20 backdrop-blur-sm flex items-center justify-center">
<i data-lucide="building" class="w-8 h-8"></i>
</div>
<div>
<h1 class="text-2xl md:text-3xl font-bold">{% trans "Agency Dashboard" %}</h1>
<p class="text-white/80">
{% trans "Welcome back" %}, {{ agency.name }}! {% trans "Welcome back" %}, {{ agency.name }}!
</p> </p>
</div> </div>
<div> </div>
{% comment %} <a href="{% url 'agency_portal_submit_application' %}" class="btn btn-main-action me-2"> </div>
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %}
</a>
>
{% endif %}
</a> {% endcomment %}
</div> </div>
</div> </div>
<!-- Overview Statistics --> <!-- Statistics Cards -->
<div class="row mb-4"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="col-md-3 mb-2"> <!-- Total Assignments -->
<div class="kaauh-card shadow-sm h-100"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="card-body text-center px-2 py-2"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<div class="text-primary-theme mb-2"> <div class="flex items-center gap-3">
<i class="fas fa-briefcase fa-2x"></i> <i data-lucide="briefcase" class="w-6 h-6"></i>
</div> <span class="text-sm font-semibold">{% trans "Total Assignments" %}</span>
<h4 class="card-title">{{ total_assignments }}</h4>
<p class="card-text text-muted">{% trans "Total Assignments" %}</p>
</div> </div>
</div> </div>
</div> <div class="p-5 text-center">
<div class="col-md-3 mb-2"> <div class="text-3xl font-bold text-temple-red mb-1">{{ total_assignments }}</div>
<div class="kaauh-card shadow-sm h-100 px-2 py-2"> <p class="text-xs text-gray-500 uppercase tracking-wide">{% trans "Assigned Jobs" %}</p>
<div class="card-body text-center">
<div class="text-primary-theme mb-2">
<i class="fas fa-check-circle fa-2x"></i>
</div>
<h4 class="card-title">{{ active_assignments }}</h4>
<p class="card-text text-muted">{% trans "Active Assignments" %}</p>
</div> </div>
</div> </div>
</div>
<div class="col-md-3 mb-2"> <!-- Active Assignments -->
<div class="kaauh-card shadow-sm h-100 px-2 py-2"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="card-body text-center"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<div class="text-primary-theme mb-2"> <div class="flex items-center gap-3">
<i class="fas fa-users fa-2x"></i> <i data-lucide="check-circle" class="w-6 h-6"></i>
</div> <span class="text-sm font-semibold">{% trans "Active Assignments" %}</span>
<h4 class="card-title">{{ total_applications }}</h4>
<p class="card-text text-muted">{% trans "Total Applications" %}</p>
</div> </div>
</div> </div>
<div class="p-5 text-center">
<div class="text-3xl font-bold text-temple-red mb-1">{{ active_assignments }}</div>
<p class="text-xs text-gray-500 uppercase tracking-wide">{% trans "In Progress" %}</p>
</div> </div>
<div class="col-md-3 mb-2">
<div class="kaauh-card shadow-sm h-100 px-2 py-2">
<div class="card-body text-center">
<div class="text-warning mb-2">
<i class="fas fa-envelope fa-2x"></i>
</div> </div>
<h4 class="card-title">{{ total_unread_messages }}</h4>
<p class="card-text text-muted">{% trans "Unread Messages" %}</p> <!-- Total Applications -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<div class="flex items-center gap-3">
<i data-lucide="users" class="w-6 h-6"></i>
<span class="text-sm font-semibold">{% trans "Total Applications" %}</span>
</div> </div>
</div> </div>
<div class="p-5 text-center">
<div class="text-3xl font-bold text-temple-red mb-1">{{ total_applications }}</div>
<p class="text-xs text-gray-500 uppercase tracking-wide">{% trans "Candidates Submitted" %}</p>
</div>
</div>
<!-- Unread Messages -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<div class="flex items-center gap-3">
<i data-lucide="mail" class="w-6 h-6"></i>
<span class="text-sm font-semibold">{% trans "Unread Messages" %}</span>
</div>
</div>
<div class="p-5 text-center">
<div class="text-3xl font-bold text-yellow-600 mb-1">{{ total_unread_messages }}</div>
<p class="text-xs text-gray-500 uppercase tracking-wide">{% trans "New Communications" %}</p>
</div>
</div> </div>
</div> </div>
<!-- Job Assignments List --> <!-- Job Assignments List -->
<div class="kaauh-card shadow-sm px-3 py-3"> <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="card-body"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-5">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="flex justify-between items-center">
<h5 class="card-title mb-0"> <div class="flex items-center gap-3">
<i class="fas fa-tasks me-2"></i> <i data-lucide="list-todo" class="w-6 h-6"></i>
{% trans "Your Job Assignments" %} <h2 class="text-lg font-bold">{% trans "Your Job Assignments" %}</h2>
</h5> </div>
<span class="badge bg-secondary">{{ assignment_stats|length }} {% trans "assignments" %}</span> <span class="inline-flex items-center px-3 py-1.5 rounded-lg bg-white/20 backdrop-blur-sm text-sm font-semibold">
{{ assignment_stats|length }} {% trans "assignments" %}
</span>
</div>
</div> </div>
<div class="p-6">
{% if assignment_stats %} {% if assignment_stats %}
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
{% for stats in assignment_stats %} {% for stats in assignment_stats %}
<div class="col-lg-6 col-xl-4 mb-4"> <div class="border border-gray-200 rounded-xl overflow-hidden hover:border-temple-red transition-all duration-200 hover:shadow-lg">
<div class="card h-100 border-0 shadow-sm assignment-card">
<div class="card-body">
<!-- Assignment Header --> <!-- Assignment Header -->
<div class="d-flex justify-content-between align-items-start mb-3"> <div class="bg-temple-cream border-b border-gray-200 p-4">
<div class="flex-grow-1"> <div class="flex justify-between items-start gap-2 mb-2">
<h6 class="card-title mb-1"> <div class="flex-1">
<h5 class="font-bold text-temple-dark mb-1">
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}" <a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
class="text-decoration-none text-dark"> class="text-temple-dark hover:text-temple-red transition">
{{ stats.assignment.job.title }} {{ stats.assignment.job.title }}
</a> </a>
</h6> </h5>
<p class="text-muted small mb-2"> <p class="text-sm text-gray-600">
<i class="fas fa-building me-1"></i> <i data-lucide="building-2" class="w-3 h-3 inline mr-1"></i>
{{ stats.assignment.job.department }} {{ stats.assignment.job.department|default:"N/A" }}
</p> </p>
</div> </div>
<div class="text-end"> <div class="shrink-0">
{% if stats.is_active %} {% if stats.is_active %}
<span class="badge bg-success">{% trans "Active" %}</span> <span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide bg-green-600 text-white">
{% trans "Active" %}
</span>
{% elif stats.assignment.status == 'COMPLETED' %} {% elif stats.assignment.status == 'COMPLETED' %}
<span class="badge bg-primary">{% trans "Completed" %}</span> <span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide bg-gray-600 text-white">
{% trans "Completed" %}
</span>
{% elif stats.assignment.status == 'CANCELLED' %} {% elif stats.assignment.status == 'CANCELLED' %}
<span class="badge bg-danger">{% trans "Cancelled" %}</span> <span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide bg-red-600 text-white">
{% trans "Cancelled" %}
</span>
{% else %} {% else %}
<span class="badge bg-warning">{% trans "Expired" %}</span> <span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide bg-yellow-600 text-white">
{% trans "Expired" %}
</span>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
<!-- Assignment Details --> <!-- Assignment Body -->
<div class="row mb-3"> <div class="p-4 space-y-3">
<div class="col-6"> <!-- Deadline & Applications -->
<small class="text-muted d-block">{% trans "Deadline" %}</small> <div class="grid grid-cols-2 gap-3">
<strong class="{% if stats.days_remaining <= 3 %}text-danger{% elif stats.days_remaining <= 7 %}text-warning{% else %}text-success{% endif %}"> <div>
<div class="text-xs text-gray-500 mb-1">{% trans "Deadline" %}</div>
<div class="font-semibold {% if stats.days_remaining <= 3 %}text-red-600{% elif stats.days_remaining <= 7 %}text-yellow-600{% else %}text-green-600{% endif %}">
{{ stats.assignment.deadline|date:"Y-m-d" }} {{ stats.assignment.deadline|date:"Y-m-d" }}
<div class="text-xs text-gray-500 font-normal">
{% if stats.days_remaining >= 0 %} {% if stats.days_remaining >= 0 %}
({{ stats.days_remaining }} {% trans "days left" %}) ({{ stats.days_remaining }} {% trans "days left" %})
{% else %} {% else %}
({{ stats.days_remaining }} {% trans "days overdue" %}) ({{ stats.days_remaining }} {% trans "days overdue" %})
{% endif %} {% endif %}
</strong>
</div> </div>
<div class="col-6"> </div>
<small class="text-muted d-block">{% trans "Applications" %}</small> </div>
<strong>{{ stats.application_count }} / {{ stats.assignment.max_candidates}}</strong> <div>
<div class="text-xs text-gray-500 mb-1">{% trans "Applications" %}</div>
<div class="font-semibold">
{{ stats.application_count }} / {{ stats.assignment.max_candidates }}
</div>
</div> </div>
</div> </div>
<!-- Progress Bar --> <!-- Progress Bar -->
<div class="mb-3"> <div>
<div class="d-flex justify-content-between mb-1"> <div class="flex justify-between text-xs text-gray-500 mb-1">
<small class="text-muted">{% trans "Submission Progress" %}</small> <span>{% trans "Submission Progress" %}</span>
<small class="text-muted">{{ stats.application_count }}/{{ stats.assignment.max_candidates }}</small> <span>{{ stats.application_count }}/{{ stats.assignment.max_candidates }}</span>
</div> </div>
<div class="progress" style="height: 6px;"> <div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
{% with progress=stats.application_count %} {% with progress=stats.application_count %}
<div class="progress-bar {% if progress >= 90 %}bg-danger{% elif progress >= 70 %}bg-warning{% else %}bg-success{% endif %}" {% widthratio progress stats.assignment.max_candidates 100 as percentage %}
style="width: {{ progress|floatformat:0 }}%"></div> <div class="h-full rounded-full transition-all duration-300 {% if percentage >= 90 %}bg-red-600{% elif percentage >= 70 %}bg-yellow-500{% else %}bg-temple-red{% endif %}"
style="width: {{ percentage }}%"></div>
{% endwith %} {% endwith %}
</div> </div>
</div> </div>
<!-- Action Buttons --> <!-- Action Buttons -->
<div class="d-flex justify-content-between align-items-center"> <div class="flex gap-2 pt-2 border-t border-gray-100">
<div>
{% if stats.can_submit %} {% if stats.can_submit %}
<a href="{% url 'agency_portal_submit_application_page' stats.assignment.slug %}" <a href="{% url 'agency_portal_submit_application_page' stats.assignment.slug %}"
class="btn btn-sm btn-main-action"> class="flex-1 inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2 rounded-lg text-sm transition">
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %} <i data-lucide="user-plus" class="w-4 h-4"></i>
{% trans "Submit" %}
</a> </a>
{% else %} {% else %}
<button class="btn btn-sm btn-secondary" disabled> <button class="flex-1 inline-flex items-center justify-center gap-2 bg-gray-200 text-gray-600 font-medium px-4 py-2 rounded-lg text-sm cursor-not-allowed" disabled>
<i class="fas fa-user-plus me-1"></i> {% trans "Submissions Closed" %} <i data-lucide="x" class="w-4 h-4"></i>
{% trans "Closed" %}
</button> </button>
{% endif %} {% endif %}
</div>
<div>
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}" <a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
class="btn btn-sm btn-main-action"> class="inline-flex items-center justify-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white font-medium px-4 py-2 rounded-lg text-sm transition">
<i class="fas fa-eye me-1"></i> {% trans "View Details" %} <i data-lucide="eye" class="w-4 h-4"></i>
{% trans "View" %}
</a> </a>
{% comment %} {% if stats.unread_messages > 0 %}
<a href="{% url 'message_list' %}"
class="btn btn-sm btn-outline-warning position-relative">
<i class="fas fa-envelope me-1"></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
{{ stats.unread_messages }}
</span>
</a>
{% endif %} {% endcomment %}
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -221,10 +206,10 @@
</div> </div>
{% include "includes/paginator.html" %} {% include "includes/paginator.html" %}
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-12">
<i class="fas fa-briefcase fa-3x text-muted mb-3"></i> <i data-lucide="briefcase" class="w-20 h-20 text-gray-300 mx-auto mb-4"></i>
<h5 class="text-muted">{% trans "No Job Assignments Found" %}</h5> <h5 class="text-xl font-bold text-gray-900 mb-2">{% trans "No Job Assignments Found" %}</h5>
<p class="text-muted"> <p class="text-gray-500 max-w-md mx-auto">
{% trans "You don't have any job assignments yet. Please contact the administrator if you expect to have assignments." %} {% trans "You don't have any job assignments yet. Please contact the administrator if you expect to have assignments." %}
</p> </p>
</div> </div>
@ -234,24 +219,16 @@
</div> </div>
<style> <style>
.assignment-card { .card-hover {
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out; transition: all 0.2s ease-in-out;
} }
.assignment-card:hover { .card-hover:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
} }
.assignment-card .card-title a:hover { .progress-bar-animated {
color: var(--kaauh-teal-dark) !important;
}
.progress {
background-color: #e9ecef;
}
.progress-bar {
transition: width 0.3s ease; transition: width 0.3s ease;
} }
</style> </style>
@ -259,11 +236,13 @@
{% block customJS %} {% block customJS %}
<script> <script>
lucide.createIcons();
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Auto-refresh for unread messages count // Auto-refresh for unread messages count
setInterval(function() { setInterval(function() {
// You could implement a lightweight API call here to check for new messages // You could implement a lightweight API call here to check for new messages
// For now, just refresh the page every 5 minutes // For now, just refresh page every 5 minutes
location.reload(); location.reload();
}, 300000); // 5 minutes }, 300000); // 5 minutes
}); });

View File

@ -1,294 +1,191 @@
{% extends 'portal_base.html' %} {% extends 'portal_base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% trans "Agency Portal Login" %} - ATS{% endblock %} {% block title %}{% trans "Agency Portal Login" %} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--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: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
min-height: 100vh;
}
.login-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 0;
}
.login-card {
background: white;
border-radius: 1rem;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
border: none;
max-width: 650px;
width: 100%;
margin: 0 1rem;
}
.login-header {
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
color: white;
padding: 2rem;
border-radius: 1rem 1rem 0 0;
text-align: center;
}
.login-body {
padding: 2.5rem;
}
.form-control:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
}
.btn-login {
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
border: none;
color: white;
font-weight: 600;
padding: 0.75rem 2rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.btn-login:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 99, 110, 0.3);
}
.input-group-text {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
}
.feature-icon {
width: 60px;
height: 60px;
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
margin-bottom: 1rem;
}
.info-section {
background-color: #f8f9fa;
border-radius: 0.5rem;
padding: 1.5rem;
margin-top: 2rem;
}
.alert {
border-radius: 0.5rem;
border: none;
}
.alert-danger {
background-color: #f8d7da;
color: #721c24;
}
.alert-info {
background-color: #d1ecf1;
color: #0c5460;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="login-container"> <body class="bg-gradient-to-br from-temple-red to-[#7a1a29] min-h-screen">
<div class="login-card"> <div class="min-h-screen flex items-center justify-center p-6">
<div class="bg-white rounded-2xl shadow-2xl border-none max-w-2xl w-full">
<!-- Login Header --> <!-- Login Header -->
<div class="login-header"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-8 rounded-t-2xl text-center">
<div class="mb-3"> <div class="mb-4 inline-flex items-center justify-center w-20 h-20 rounded-full bg-white/20 backdrop-blur-sm">
<i class="fas fa-building fa-3x"></i> <i data-lucide="building" class="w-10 h-10"></i>
</div> </div>
<h3 class="mb-2">{% trans "Agency Portal" %}</h3> <h2 class="text-2xl md:text-3xl font-bold mb-2">{% trans "Agency Portal" %}</h2>
<p class="mb-0 opacity-75"> <p class="text-white/80 text-sm md:text-base">
{% trans "Submit candidates for job assignments" %} {% trans "Submit candidates for job assignments" %}
</p> </p>
</div> </div>
<!-- Login Body --> <!-- Login Body -->
<div class="login-body"> <div class="p-8 md:p-10">
<!-- Messages -->
<!-- Login Form --> <!-- Login Form -->
<form method="post" novalidate> <form method="post" novalidate id="login-form" class="space-y-6">
{% csrf_token %} {% csrf_token %}
<!-- Access Token Field --> <!-- Access Token Field -->
<div class="mb-3"> <div class="space-y-2">
<label for="{{ form.token.id_for_label }}" class="form-label fw-bold"> <label for="{{ form.token.id_for_label }}" class="block text-sm font-bold text-gray-700">
<i class="fas fa-key me-2"></i> <i data-lucide="key" class="w-4 h-4 inline mr-2"></i>
{% trans "Access Token" %} {% trans "Access Token" %}
</label> </label>
<div class="input-group"> <div class="relative">
<span class="input-group-text"> <span class="absolute left-4 top-1/2 -translate-y-1/2 text-temple-red">
<i class="fas fa-lock"></i> <i data-lucide="lock" class="w-5 h-5"></i>
</span> </span>
{{ form.token }} <input type="text"
name="token"
id="{{ form.token.id_for_label }}"
class="w-full pl-12 pr-4 py-3.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'Enter your access token' %}"
required>
</div> </div>
{% if form.token.errors %} {% if form.token.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-4 h-4"></i>
{% for error in form.token.errors %}{{ error }}{% endfor %} {% for error in form.token.errors %}{{ error }}{% endfor %}
</div> </div>
{% endif %} {% endif %}
<small class="form-text text-muted"> <p class="text-xs text-gray-500">
{% trans "Enter the access token provided by the hiring organization" %} {% trans "Enter the access token provided by the hiring organization" %}
</small> </p>
</div> </div>
<!-- Password Field --> <!-- Password Field -->
<div class="mb-4"> <div class="space-y-2">
<label for="{{ form.password.id_for_label }}" class="form-label fw-bold"> <label for="{{ form.password.id_for_label }}" class="block text-sm font-bold text-gray-700">
<i class="fas fa-shield-alt me-2"></i> <i data-lucide="shield-check" class="w-4 h-4 inline mr-2"></i>
{% trans "Password" %} {% trans "Password" %}
</label> </label>
<div class="input-group"> <div class="relative">
<span class="input-group-text"> <span class="absolute left-4 top-1/2 -translate-y-1/2 text-temple-red">
<i class="fas fa-key"></i> <i data-lucide="key" class="w-5 h-5"></i>
</span> </span>
{{ form.password }} <input type="password"
name="password"
id="{{ form.password.id_for_label }}"
class="w-full pl-12 pr-12 py-3.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
placeholder="{% trans 'Enter your password' %}"
required>
<button type="button"
onclick="togglePassword()"
id="password-toggle"
class="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition">
<i data-lucide="eye" class="w-5 h-5"></i>
</button>
</div> </div>
{% if form.password.errors %} {% if form.password.errors %}
<div class="text-danger small mt-1"> <div class="text-red-600 text-sm mt-1 flex items-center gap-1">
<i data-lucide="alert-circle" class="w-4 h-4"></i>
{% for error in form.password.errors %}{{ error }}{% endfor %} {% for error in form.password.errors %}{{ error }}{% endfor %}
</div> </div>
{% endif %} {% endif %}
<small class="form-text text-muted"> <p class="text-xs text-gray-500">
{% trans "Enter the password for this access token" %} {% trans "Enter the password for this access token" %}
</small> </p>
</div> </div>
<!-- Submit Button --> <!-- Submit Button -->
<div class="d-grid"> <button type="submit"
<button type="submit" class="btn btn-login btn-lg"> class="w-full bg-gradient-to-r from-temple-red to-[#7a1a29] hover:from-[#8a1d2d] hover:to-[#6a1520] text-white font-bold py-4 rounded-xl text-sm transition shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 flex items-center justify-center gap-2">
<i class="fas fa-sign-in-alt me-2"></i> <i data-lucide="log-in" class="w-5 h-5"></i>
{% trans "Access Portal" %} {% trans "Access Portal" %}
</button> </button>
</div>
</form> </form>
<!-- Information Section --> <!-- Information Section -->
<div class="info-section"> <div class="mt-8 bg-temple-cream rounded-xl p-6 border border-gray-200">
<h6 class="fw-bold mb-3" style="color: var(--kaauh-teal-dark);"> <h5 class="text-temple-dark font-bold mb-4 flex items-center gap-2">
<i class="fas fa-info-circle me-2"></i> <i data-lucide="help-circle" class="w-5 h-5 text-temple-red"></i>
{% trans "Need Help?" %} {% trans "Need Help?" %}
</h6> </h5>
<div class="row text-center"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-center">
<div class="col-6 mb-3"> <div>
<div class="feature-icon mx-auto"> <div class="w-16 h-16 mx-auto mb-3 rounded-full bg-gradient-to-br from-temple-red to-[#7a1a29] flex items-center justify-center text-white">
<i class="fas fa-envelope"></i> <i data-lucide="mail" class="w-8 h-8"></i>
</div> </div>
<h6 class="fw-bold">{% trans "Contact Support" %}</h6> <h6 class="font-bold text-gray-900 mb-1">{% trans "Contact Support" %}</h6>
<small class="text-muted"> <p class="text-sm text-gray-500">
{% trans "Reach out to your hiring contact" %} {% trans "Reach out to your hiring contact" %}
</small> </p>
</div> </div>
<div class="col-6 mb-3"> <div>
<div class="feature-icon mx-auto"> <div class="w-16 h-16 mx-auto mb-3 rounded-full bg-gradient-to-br from-temple-red to-[#7a1a29] flex items-center justify-center text-white">
<i class="fas fa-question-circle"></i> <i data-lucide="book-open" class="w-8 h-8"></i>
</div> </div>
<h6 class="fw-bold">{% trans "Documentation" %}</h6> <h6 class="font-bold text-gray-900 mb-1">{% trans "Documentation" %}</h6>
<small class="text-muted"> <p class="text-sm text-gray-500">
{% trans "View user guides and tutorials" %} {% trans "View user guides and tutorials" %}
</small> </p>
</div> </div>
</div> </div>
</div> </div>
<!-- Security Notice --> <!-- Security Notice -->
<div class="alert alert-info mt-3 mb-0"> <div class="mt-6 bg-blue-50 border border-blue-200 rounded-xl p-5">
<h6 class="alert-heading"> <h6 class="text-blue-800 font-bold mb-3 flex items-center gap-2">
<i class="fas fa-shield-alt me-2"></i> <i data-lucide="shield-check" class="w-4 h-4"></i>
{% trans "Security Notice" %} {% trans "Security Notice" %}
</h6> </h6>
<p class="mb-2 small"> <p class="text-blue-700 text-sm mb-3">
{% trans "This portal is for authorized agency partners only. Access is monitored and logged." %} {% trans "This portal is for authorized agency partners only. Access is monitored and logged." %}
</p> </p>
<hr> <hr class="border-blue-200 mb-3">
<p class="mb-0 small"> <p class="text-blue-700 text-sm mb-0">
{% trans "If you believe you've received this link in error, please contact the hiring organization immediately." %} {% trans "If you believe you've received this link in error, please contact the hiring organization immediately." %}
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Toast Notification Container -->
<div id="toast-container"></div>
</body>
{% endblock %} {% endblock %}
{% block customJS %} {% block customJS %}
<script> <script>
document.addEventListener('DOMContentLoaded', function() { lucide.createIcons();
// Focus on access token field
const accessTokenField = document.getElementById('{{ form.token.id_for_label }}');
if (accessTokenField) {
accessTokenField.focus();
}
// Auto-format access token (remove spaces and convert to uppercase) // Auto-format access token (remove spaces and convert to uppercase)
const accessTokenInput = document.getElementById('{{ form.token.id_for_label }}'); const accessTokenInput = document.getElementById('{{ form.token.id_for_label }}');
if (accessTokenInput) { if (accessTokenInput) {
accessTokenInput.addEventListener('input', function(e) { accessTokenInput.addEventListener('input', function() {
// Remove spaces and convert to uppercase // Remove spaces and convert to uppercase
this.value = this.value.replace(/\s+/g, ''); this.value = this.value.replace(/\s+/g, '').toUpperCase();
}); });
// Focus on load
accessTokenInput.focus();
} }
// Show/hide password functionality // Toggle password visibility
function togglePassword() {
const passwordField = document.getElementById('{{ form.password.id_for_label }}'); const passwordField = document.getElementById('{{ form.password.id_for_label }}');
const passwordToggle = document.createElement('button'); const toggleBtn = document.getElementById('password-toggle');
passwordToggle.type = 'button';
passwordToggle.className = 'btn btn-outline-secondary';
passwordToggle.innerHTML = '<i class="fas fa-eye"></i>';
passwordToggle.style.position = 'absolute';
passwordToggle.style.right = '10px';
passwordToggle.style.top = '50%';
passwordToggle.style.transform = 'translateY(-50%)';
passwordToggle.style.border = 'none';
passwordToggle.style.background = 'none';
passwordToggle.style.zIndex = '10';
if (passwordField && passwordField.parentElement) { if (passwordField && toggleBtn) {
passwordField.parentElement.style.position = 'relative';
passwordToggle.addEventListener('click', function() {
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password'; const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
passwordField.setAttribute('type', type); passwordField.setAttribute('type', type);
this.innerHTML = type === 'password' ? '<i class="fas fa-eye"></i>' : '<i class="fas fa-eye-slash"></i>';
});
passwordField.parentElement.appendChild(passwordToggle); const icon = type === 'password' ? 'eye' : 'eye-off';
toggleBtn.innerHTML = `<i data-lucide="${icon}" class="w-5 h-5"></i>`;
lucide.createIcons();
}
} }
// Form validation // Form validation
const form = document.querySelector('form'); const form = document.getElementById('login-form');
if (form) { if (form) {
form.addEventListener('submit', function(e) { form.addEventListener('submit', function(e) {
const accessToken = accessTokenInput.value.trim(); const accessToken = accessTokenInput.value.trim();
const password = passwordField.value.trim(); const passwordField = document.getElementById('{{ form.password.id_for_label }}');
const password = passwordField ? passwordField.value.trim() : '';
if (!accessToken) { if (!accessToken) {
e.preventDefault(); e.preventDefault();
@ -307,29 +204,42 @@ document.addEventListener('DOMContentLoaded', function() {
} }
function showError(message) { function showError(message) {
// Remove existing alerts // Remove existing toasts
const existingAlerts = document.querySelectorAll('.alert-danger'); const container = document.getElementById('toast-container');
existingAlerts.forEach(alert => alert.remove()); container.innerHTML = '';
// Create new alert // Create new toast
const alertDiv = document.createElement('div'); const toast = document.createElement('div');
alertDiv.className = 'alert alert-danger alert-dismissible fade show'; toast.className = 'fixed top-4 right-4 bg-red-600 text-white px-6 py-4 rounded-xl shadow-lg z-50 flex items-center gap-3 animate-pulse';
alertDiv.innerHTML = ` toast.innerHTML = `
${message} <i data-lucide="alert-circle" class="w-5 h-5"></i>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button> <span class="flex-1">${message}</span>
<button onclick="this.parentElement.remove()" class="hover:bg-red-700 p-1 rounded-lg transition">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
`; `;
// Insert at the top of the login body container.appendChild(toast);
const loginBody = document.querySelector('.login-body'); lucide.createIcons();
loginBody.insertBefore(alertDiv, loginBody.firstChild);
// Auto-dismiss after 5 seconds // Auto-dismiss after 5 seconds
setTimeout(() => { setTimeout(() => {
if (alertDiv.parentNode) { if (toast.parentNode) {
alertDiv.remove(); toast.classList.add('animate-fade-out');
setTimeout(() => toast.remove(), 300);
} }
}, 5000); }, 5000);
} }
});
</script> </script>
<style>
@keyframes fade-out {
from { opacity: 1; transform: translateY(0); }
to { opacity: 0; transform: translateY(-20px); }
}
.animate-fade-out {
animation: fade-out 0.3s ease-out forwards;
}
</style>
{% endblock %} {% endblock %}

View File

@ -2,232 +2,126 @@
{% load static i18n crispy_forms_tags %} {% load static i18n crispy_forms_tags %}
{% block title %}{% trans "Agency Applicant List" %} - ATS{% endblock %} {% block title %}{% trans "Agency Applicant List" %} - ATS{% endblock %}
{% block customCSS %}
<style>
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745;
--kaauh-info: #17a2b8;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.person-row:hover {
background-color: #f8f9fa;
cursor: pointer;
}
.stage-badge {
font-size: 0.75rem;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
font-weight: 500;
}
.search-form {
background-color: white;
border: 1px solid var(--kaauh-border);
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1.5rem;
}
</style>
{% endblock%}
{% block content %} {% block content %}
<div class="container-fluid py-4 persons-list"> <div class="container mx-auto px-4 py-8">
<div class="d-flex justify-content-between align-items-center mb-4"> <!-- Header -->
<div class="px-2 py-2"> <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6 px-2 py-2">
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <div>
<i class="fas fa-users me-2"></i> <h1 class="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
<div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="users" class="w-8 h-8 text-temple-red"></i>
</div>
{% trans "All Applicants" %} {% trans "All Applicants" %}
</h1> </h1>
<p class="text-muted mb-0"> <p class="text-gray-600">{% trans "All applicants who come through" %} {{ agency.name }}</p>
{% trans "All applicants who come through" %} {{ agency.name }}
</p>
</div> </div>
<div> <button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl transition shadow-sm hover:shadow-md" data-bs-toggle="modal" data-bs-target="#personModal">
<!-- Add Person Button --> <i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add New Applicant" %}
<button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#personModal">
<i class="fas fa-plus me-1"></i> {% trans "Add New Applicant" %}
</button> </button>
</div> </div>
</div>
<!-- Search and Filter Section --> <!-- Search and Filter Section -->
<div class="kaauh-card shadow-sm mb-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
<div class="card-body"> <div class="p-6">
<form method="get" class="search-form"> <form method="get">
<div class="row g-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="col-md-6"> <div>
<label for="search" class="form-label fw-semibold"> <label for="search" class="block text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<i class="fas fa-search me-1"></i>{% trans "Search" %} <i data-lucide="search" class="w-4 h-4"></i>{% trans "Search" %}
</label> </label>
<input type="text" <input type="text"
class="form-control" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition"
id="search" id="search"
name="q" name="q"
value="{{ search_query }}" value="{{ search_query }}"
placeholder="{% trans 'Search by name, email, phone...' %}"> placeholder="{% trans 'Search by name, email, phone...' %}">
</div> </div>
{% comment %} <div class="col-md-3">
<label for="stage" class="form-label fw-semibold">
<i class="fas fa-filter me-1"></i>{% trans "Stage" %}
</label>
<select class="form-select" id="stage" name="stage">
<option value="">{% trans "All Stages" %}</option>
{% for stage_value, stage_label in stage_choices %}
<option value="{{ stage_value }}"
{% if stage_filter == stage_value %}selected{% endif %}>
{{ stage_label }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-main-action w-100">
<i class="fas fa-search me-1"></i> {% trans "Search" %}
</button>
</div> {% endcomment %}
</div> </div>
</form> </form>
</div> </div>
</div> </div>
<!-- Results Summary --> <!-- Results Summary -->
<div class="row mb-3"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<div class="col-md-6"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="kaauh-card shadow-sm h-100 p-3"> <div class="p-6 text-center">
<div class="card-body text-center"> <div class="w-12 h-12 bg-temple-red/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<div class="text-primary-theme mb-2"> <i data-lucide="users" class="w-6 h-6 text-temple-red"></i>
<i class="fas fa-users fa-2x"></i>
</div> </div>
<h4 class="card-title">{{ total_persons }}</h4> <h4 class="text-2xl font-bold text-gray-900 mb-1">{{ total_persons }}</h4>
<p class="card-text text-muted">{% trans "Total Persons" %}</p> <p class="text-gray-600">{% trans "Total Persons" %}</p>
</div> </div>
</div> </div>
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="p-6 text-center">
<div class="w-12 h-12 bg-temple-red/10 rounded-lg flex items-center justify-center mx-auto mb-3">
<i data-lucide="check-circle" class="w-6 h-6 text-temple-red"></i>
</div> </div>
<div class="col-md-6"> <h4 class="text-2xl font-bold text-gray-900 mb-1">{{ page_obj|length }}</h4>
<div class="kaauh-card shadow-sm h-100 p-3"> <p class="text-gray-600">{% trans "Showing on this page" %}</p>
<div class="card-body text-center">
<div class="text-primary-theme mb-2">
<i class="fas fa-check-circle fa-2x"></i>
</div>
<h4 class="card-title">{{ page_obj|length }}</h4>
<p class="card-text text-muted">{% trans "Showing on this page" %}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Persons Table --> <!-- Persons Table -->
<div class="kaauh-card shadow-sm"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="card-body p-0"> <div class="overflow-x-auto">
{% if page_obj %} {% if page_obj %}
<div class="table-responsive person-table"> <table class="w-full">
<table class="table table-hover mb-0"> <thead class="bg-gray-50 border-b border-gray-200">
<thead class="table-light">
<tr> <tr>
<th scope="col">{% trans "Name" %}</th> <th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Name" %}</th>
<th scope="col">{% trans "Email" %}</th> <th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Email" %}</th>
<th scope="col">{% trans "Phone" %}</th> <th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Phone" %}</th>
<th scope="col">{% trans "Created At" %}</th> <th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Created At" %}</th>
{% comment %} <th scope="col">{% trans "Stage" %}</th> <th class="text-center py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Actions" %}</th>
<th scope="col">{% trans "Applied Date" %}</th> {% endcomment %}
<th scope="col" class="text-center">{% trans "Actions" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for person in page_obj %} {% for person in page_obj %}
<tr class="person-row"> <tr class="border-b border-gray-100 hover:bg-gray-50 transition cursor-pointer">
<td> <td class="py-4 px-4">
<div class="d-flex align-items-center"> <div class="flex items-center gap-3">
<div class="rounded-circle bg-primary-theme text-white d-flex align-items-center justify-content-center me-2" <div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center font-bold text-sm">
style="width: 32px; height: 32px; font-size: 14px; font-weight: 600;">
{{ person.first_name|first|upper }}{{ person.last_name|first|upper }} {{ person.first_name|first|upper }}{{ person.last_name|first|upper }}
</div> </div>
<div> <div>
<div class="fw-semibold">{{ person.first_name }} {{ person.last_name }}</div> <div class="font-semibold text-gray-900">{{ person.first_name }} {{ person.last_name }}</div>
{% if person.address %} {% if person.address %}
<small class="text-muted">{{ person.address|truncatechars:50 }}</small> <div class="text-sm text-gray-500">{{ person.address|truncatechars:50 }}</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</td> </td>
<td> <td class="py-4 px-4">
<a href="mailto:{{ person.email }}" class="text-decoration-none text-dark"> <a href="mailto:{{ person.email }}" class="text-gray-900 hover:text-temple-red transition">{{ person.email }}</a>
{{ person.email }}
</a>
</td> </td>
<td>{{ person.phone|default:"-" }}</td> <td class="py-4 px-4">{{ person.phone|default:"-" }}</td>
{% comment %} <td> <td class="py-4 px-4">{{ person.created_at|date:"d-m-Y" }}</td>
<span class="badge bg-light text-dark"> <td class="py-4 px-4 text-center">
{{ person.job.title|truncatechars:30 }}
</span>
</td>
<td>
{% with stage_class=person.stage|lower %}
<span class="stage-badge
{% if stage_class == 'applied' %}bg-secondary{% endif %}
{% if stage_class == 'exam' %}bg-info{% endif %}
{% if stage_class == 'interview' %}bg-warning{% endif %}
{% if stage_class == 'offer' %}bg-success{% endif %}
{% if stage_class == 'hired' %}bg-primary{% endif %}
{% if stage_class == 'rejected' %}bg-danger{% endif %}
text-white">
{{ person.get_stage_display }}
</span>
{% endwith %}
</td> {% endcomment %}
<td>{{ person.created_at|date:"d-m-Y" }}</td>
<td class="text-center">
<div class="btn-group" role="group">
<button type="button" data-bs-toggle="modal" data-bs-target="#updateModal" <button type="button" data-bs-toggle="modal" data-bs-target="#updateModal"
hx-get="{% url 'person_update' person.slug %}" hx-get="{% url 'person_update' person.slug %}"
hx-target="#updateModalBody" hx-target="#updateModalBody"
hx-swap="outerrHTML" hx-swap="outerrHTML"
hx-select="#person-form" hx-select="#person-form"
hx-vals='{"view":"portal"}' hx-vals='{"view":"portal"}'
class="btn btn-sm btn-outline-secondary" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition"
title="{% trans 'Edit Person' %}" title="{% trans 'Edit Person' %}">
> <i data-lucide="edit-2" class="w-4 h-4"></i>
<i class="fas fa-edit"></i>
</button> </button>
</div>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div>
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-10">
<i class="fas fa-users fa-3x text-muted mb-3"></i> <div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<h5 class="text-muted">{% trans "No persons found" %}</h5> <i data-lucide="users" class="w-8 h-8 text-gray-400"></i>
<p class="text-muted"> </div>
<h5 class="text-gray-600 mb-2">{% trans "No persons found" %}</h5>
<p class="text-gray-500 mb-4">
{% if search_query or stage_filter %} {% if search_query or stage_filter %}
{% trans "Try adjusting your search or filter criteria." %} {% trans "Try adjusting your search or filter criteria." %}
{% else %} {% else %}
@ -236,8 +130,8 @@
</p> </p>
{% if not search_query and not stage_filter and agency.assignments.exists %} {% if not search_query and not stage_filter and agency.assignments.exists %}
<a href="{% url 'agency_portal_submit_application_page' agency.assignments.first.slug %}" <a href="{% url 'agency_portal_submit_application_page' agency.assignments.first.slug %}"
class="btn btn-main-action"> class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
<i class="fas fa-user-plus me-1"></i> {% trans "Add First Person" %} <i data-lucide="user-plus" class="w-4 h-4"></i> {% trans "Add First Person" %}
</a> </a>
{% endif %} {% endif %}
</div> </div>
@ -247,46 +141,34 @@
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<nav aria-label="{% trans 'Persons pagination' %}" class="mt-4"> <nav aria-label="{% trans 'Persons pagination' %}" class="mt-6">
<ul class="pagination justify-content-center"> <div class="flex justify-center items-center gap-2">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item"> <a href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
<a class="page-link" href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}"> <i data-lucide="chevrons-left" class="w-4 h-4"></i>
<i class="fas fa-angle-double-left"></i>
</a> </a>
</li> <a href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
<li class="page-item"> <i data-lucide="chevron-left" class="w-4 h-4"></i>
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
<i class="fas fa-angle-left"></i>
</a> </a>
</li>
{% endif %} {% endif %}
{% for num in page_obj.paginator.page_range %} {% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %} {% if page_obj.number == num %}
<li class="page-item active"> <span class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-temple-red text-white font-semibold">{{ num }}</span>
<span class="page-link">{{ num }}</span>
</li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %} {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item"> <a href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">{{ num }}</a>
<a class="page-link" href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">{{ num }}</a>
</li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="page-item"> <a href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}"> <i data-lucide="chevron-right" class="w-4 h-4"></i>
<i class="fas fa-angle-right"></i>
</a> </a>
</li> <a href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
<li class="page-item"> <i data-lucide="chevrons-right" class="w-4 h-4"></i>
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
<i class="fas fa-angle-double-right"></i>
</a> </a>
</li>
{% endif %} {% endif %}
</ul> </div>
</nav> </nav>
{% endif %} {% endif %}
</div> </div>
@ -303,79 +185,77 @@
<div class="modal-body" id="updateModalBody"> <div class="modal-body" id="updateModalBody">
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<div class="d-flex gap-2"> <div class="flex gap-2">
<button form="person-form" type="submit" class="btn btn-main-action"> <button form="person-form" type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition">
<i class="fas fa-save me-1"></i> {% trans "Update" %} <i data-lucide="save" class="w-4 h-4"></i> {% trans "Update" %}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Person Modal --> <!-- Person Modal -->
<div class="modal fade modal-lg" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true"> <div class="modal fade" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="personModalLabel"> <h5 class="modal-title flex items-center gap-2" id="personModalLabel">
<i class="fas fa-users me-2"></i> <i data-lucide="users" class="w-5 h-5"></i>
{% trans "Applicant Details" %} {% trans "Applicant Details" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="modal-body" id="personModalBody"> <div class="modal-body" id="personModalBody">
<form id="person_form" method="post" action="{% url 'person_create' %}" > <form id="person_form" method="post" action="{% url 'person_create' %}" >
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="view" value="portal"> <input type="hidden" name="view" value="portal">
<input type="hidden" name="agency" value="{{ agency.slug }}"> <input type="hidden" name="agency" value="{{ agency.slug }}">
<div class="row g-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="col-md-4"> <div>
{{ person_form.first_name|as_crispy_field }} {{ person_form.first_name|as_crispy_field }}
</div> </div>
<div class="col-md-4"> <div>
{{ person_form.middle_name|as_crispy_field }} {{ person_form.middle_name|as_crispy_field }}
</div> </div>
<div class="col-md-4"> <div>
{{ person_form.last_name|as_crispy_field }} {{ person_form.last_name|as_crispy_field }}
</div> </div>
</div> </div>
<div class="row g-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="col-md-6"> <div>
{{ person_form.email|as_crispy_field }} {{ person_form.email|as_crispy_field }}
{{person_form.errors}} {{person_form.errors}}
</div> </div>
<div class="col-md-6"> <div>
{{ person_form.phone|as_crispy_field }} {{ person_form.phone|as_crispy_field }}
</div> </div>
</div> </div>
<div class="row g-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="col-md-6"> <div>
{{ person_form.gpa|as_crispy_field }} {{ person_form.gpa|as_crispy_field }}
</div> </div>
<div class="col-md-6"> <div>
{{ person_form.national_id|as_crispy_field }} {{ person_form.national_id|as_crispy_field }}
</div> </div>
</div> </div>
<div class="row g-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="col-md-6"> <div>
{{ person_form.date_of_birth|as_crispy_field }} {{ person_form.date_of_birth|as_crispy_field }}
</div> </div>
<div class="col-md-6"> <div>
{{ person_form.nationality|as_crispy_field }} {{ person_form.nationality|as_crispy_field }}
</div> </div>
</div> </div>
<div class="row g-4"> <div>
<div class="col-12">
{{ person_form.address|as_crispy_field }} {{ person_form.address|as_crispy_field }}
</div> </div>
</div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn btn-main-action" type="submit" form="person_form">{% trans "Save" %}</button> <button class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition" type="submit" form="person_form">{% trans "Save" %}</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> <button type="button" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl transition" data-bs-dismiss="modal">
{% trans "Close" %} {% trans "Close" %}
</button> </button>
</div> </div>
@ -389,36 +269,13 @@ function openPersonModal(personId, personName) {
document.getElementById('person-modal-text').innerHTML = `<strong>${personName}</strong> (ID: ${personId})`; document.getElementById('person-modal-text').innerHTML = `<strong>${personName}</strong> (ID: ${personId})`;
modal.show(); modal.show();
} }
</script>
<script>
function editPerson(personId) { function editPerson(personId) {
// Placeholder for edit functionality
// This would typically open a modal or navigate to edit page
console.log('Edit person:', personId); console.log('Edit person:', personId);
// For now, you can redirect to a placeholder edit URL
// window.location.href = `/portal/candidates/${personId}/edit/`;
} }
// Auto-submit form on filter change document.addEventListener('DOMContentLoaded', function() {
document.getElementById('stage').addEventListener('change', function() { lucide.createIcons();
this.form.submit();
});
// Add row click functionality
document.querySelectorAll('.person-row').forEach(row => {
row.addEventListener('click', function(e) {
// Don't trigger if clicking on buttons or links
if (e.target.closest('a, button')) {
return;
}
// Find the view details button and click it
const viewBtn = this.querySelector('a[title*="View"]');
if (viewBtn) {
viewBtn.click();
}
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -11,7 +11,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="px-4 py-6">
{# Header #} {# Header #}
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6"> <div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
@ -25,7 +25,7 @@
<div class="flex flex-col sm:flex-row items-center text-center sm:text-start gap-4"> <div class="flex flex-col sm:flex-row items-center text-center sm:text-start gap-4">
<img src="{% if applicant.user.profile_image %}{{ applicant.user.profile_image.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}" <img src="{% if applicant.user.profile_image %}{{ applicant.user.profile_image.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}"
alt="{% trans 'Profile Picture' %}" alt="{% trans 'Profile Picture' %}"
class="w-20 h-20 rounded-full border-4 border-kaauh-blue/20 object-cover shadow-lg"> class="w-20 h-20 rounded-full border-4 border-temple-red/20 object-cover shadow-lg">
<div class="flex-1"> <div class="flex-1">
<h3 class="text-lg font-bold text-gray-900 mb-1">{{ applicant.full_name|default:"Applicant Name" }}</h3> <h3 class="text-lg font-bold text-gray-900 mb-1">{{ applicant.full_name|default:"Applicant Name" }}</h3>
<p class="text-sm text-gray-600">{{ applicant.email }}</p> <p class="text-sm text-gray-600">{{ applicant.email }}</p>
@ -39,19 +39,19 @@
{# Tab Navigation #} {# Tab Navigation #}
<div class="border-b border-gray-200 overflow-x-auto"> <div class="border-b border-gray-200 overflow-x-auto">
<div class="flex min-w-max px-4 pt-4 gap-2"> <div class="flex min-w-max px-4 pt-4 gap-2">
<button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-50 transition" <button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-temple-red text-temple-red bg-temple-red/5 transition"
onclick="showTab('profile-details')" onclick="showTab('profile-details')"
data-tab="profile-details"> data-tab="profile-details">
<i data-lucide="user-circle" class="w-4 h-4 mr-2 inline"></i> <i data-lucide="user-circle" class="w-4 h-4 mr-2 inline"></i>
{% trans "Profile Details" %} {% trans "Profile Details" %}
</button> </button>
<button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-50 transition" <button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:border-temple-red hover:text-temple-red hover:bg-temple-red/5 transition"
onclick="showTab('applications-history')" onclick="showTab('applications-history')"
data-tab="applications-history"> data-tab="applications-history">
<i data-lucide="list-alt" class="w-4 h-4 mr-2 inline"></i> <i data-lucide="list-alt" class="w-4 h-4 mr-2 inline"></i>
{% trans "My Applications" %} {% trans "My Applications" %}
</button> </button>
<button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-50 transition" <button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:border-temple-red hover:text-temple-red hover:bg-temple-red/5 transition"
onclick="showTab('document-management')" onclick="showTab('document-management')"
data-tab="document-management"> data-tab="document-management">
<i data-lucide="file-upload" class="w-4 h-4 mr-2 inline"></i> <i data-lucide="file-upload" class="w-4 h-4 mr-2 inline"></i>
@ -69,20 +69,20 @@
<!-- Basic Information Section --> <!-- Basic Information Section -->
<div class="mb-8"> <div class="mb-8">
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2"> <h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="user" class="w-5 h-5 text-kaauh-blue"></i> <i data-lucide="user" class="w-5 h-5 text-temple-red"></i>
{% trans "Basic Information" %} {% trans "Basic Information" %}
</h4> </h4>
<div class="bg-gray-50 rounded-xl p-4 space-y-3"> <div class="bg-gray-50 rounded-xl p-4 space-y-3">
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="badge" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="badge" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "First Name" %}</span> <span class="font-semibold text-gray-700">{% trans "First Name" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.first_name|default:"" }}</span> <span class="text-gray-900 font-medium">{{ applicant.first_name|default:"" }}</span>
</div> </div>
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="badge" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="badge" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "Last Name" %}</span> <span class="font-semibold text-gray-700">{% trans "Last Name" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.last_name|default:"" }}</span> <span class="text-gray-900 font-medium">{{ applicant.last_name|default:"" }}</span>
@ -90,7 +90,7 @@
{% if applicant.middle_name %} {% if applicant.middle_name %}
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="badge" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="badge" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "Middle Name" %}</span> <span class="font-semibold text-gray-700">{% trans "Middle Name" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.middle_name }}</span> <span class="text-gray-900 font-medium">{{ applicant.middle_name }}</span>
@ -98,7 +98,7 @@
{% endif %} {% endif %}
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="mail" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="mail" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "Email" %}</span> <span class="font-semibold text-gray-700">{% trans "Email" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.email|default:"" }}</span> <span class="text-gray-900 font-medium">{{ applicant.email|default:"" }}</span>
@ -109,13 +109,13 @@
<!-- Contact Information Section --> <!-- Contact Information Section -->
<div class="mb-8"> <div class="mb-8">
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2"> <h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="address-book" class="w-5 h-5 text-kaauh-blue"></i> <i data-lucide="address-book" class="w-5 h-5 text-temple-red"></i>
{% trans "Contact Information" %} {% trans "Contact Information" %}
</h4> </h4>
<div class="bg-gray-50 rounded-xl p-4 space-y-3"> <div class="bg-gray-50 rounded-xl p-4 space-y-3">
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="phone" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="phone" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "Phone" %}</span> <span class="font-semibold text-gray-700">{% trans "Phone" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.phone|default:"" }}</span> <span class="text-gray-900 font-medium">{{ applicant.phone|default:"" }}</span>
@ -123,7 +123,7 @@
{% if applicant.address %} {% if applicant.address %}
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="map-pin" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="map-pin" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "Address" %}</span> <span class="font-semibold text-gray-700">{% trans "Address" %}</span>
</div> </div>
<span class="text-gray-900 font-medium text-right max-w-xs">{{ applicant.address|linebreaksbr }}</span> <span class="text-gray-900 font-medium text-right max-w-xs">{{ applicant.address|linebreaksbr }}</span>
@ -132,10 +132,10 @@
{% if applicant.linkedin_profile %} {% if applicant.linkedin_profile %}
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="linkedin" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="linkedin" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "LinkedIn Profile" %}</span> <span class="font-semibold text-gray-700">{% trans "LinkedIn Profile" %}</span>
</div> </div>
<a href="{{ applicant.linkedin_profile }}" target="_blank" class="text-kaauh-blue hover:text-kaauh-blue/80 font-medium flex items-center gap-1"> <a href="{{ applicant.linkedin_profile }}" target="_blank" class="text-temple-red hover:text-temple-red-dark font-medium flex items-center gap-1">
{% trans "View Profile" %} <i data-lucide="external-link" class="w-3 h-3"></i> {% trans "View Profile" %} <i data-lucide="external-link" class="w-3 h-3"></i>
</a> </a>
</div> </div>
@ -146,27 +146,27 @@
<!-- Personal Details Section --> <!-- Personal Details Section -->
<div class="mb-8"> <div class="mb-8">
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2"> <h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="user-circle" class="w-5 h-5 text-kaauh-blue"></i> <i data-lucide="user-circle" class="w-5 h-5 text-temple-red"></i>
{% trans "Personal Details" %} {% trans "Personal Details" %}
</h4> </h4>
<div class="bg-gray-50 rounded-xl p-4 space-y-3"> <div class="bg-gray-50 rounded-xl p-4 space-y-3">
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="calendar" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="calendar" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "Date of Birth" %}</span> <span class="font-semibold text-gray-700">{% trans "Date of Birth" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.date_of_birth|date:"M d, Y"|default:"" }}</span> <span class="text-gray-900 font-medium">{{ applicant.date_of_birth|date:"M d, Y"|default:"" }}</span>
</div> </div>
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="venus-mars" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="venus-mars" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "Gender" %}</span> <span class="font-semibold text-gray-700">{% trans "Gender" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.get_gender_display|default:"" }}</span> <span class="text-gray-900 font-medium">{{ applicant.get_gender_display|default:"" }}</span>
</div> </div>
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="globe" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="globe" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "Nationality" %}</span> <span class="font-semibold text-gray-700">{% trans "Nationality" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.get_nationality_display|default:"" }}</span> <span class="text-gray-900 font-medium">{{ applicant.get_nationality_display|default:"" }}</span>
@ -177,14 +177,14 @@
<!-- Professional Information Section --> <!-- Professional Information Section -->
<div class="mb-8"> <div class="mb-8">
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2"> <h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="briefcase" class="w-5 h-5 text-kaauh-blue"></i> <i data-lucide="briefcase" class="w-5 h-5 text-temple-red"></i>
{% trans "Professional Information" %} {% trans "Professional Information" %}
</h4> </h4>
<div class="bg-gray-50 rounded-xl p-4 space-y-3"> <div class="bg-gray-50 rounded-xl p-4 space-y-3">
{% if applicant.user.designation %} {% if applicant.user.designation %}
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="user-tie" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="user-tie" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "Designation" %}</span> <span class="font-semibold text-gray-700">{% trans "Designation" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.user.designation }}</span> <span class="text-gray-900 font-medium">{{ applicant.user.designation }}</span>
@ -193,7 +193,7 @@
{% if applicant.gpa %} {% if applicant.gpa %}
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0"> <div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<div class="flex items-center gap-2 text-gray-600"> <div class="flex items-center gap-2 text-gray-600">
<i data-lucide="graduation-cap" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="graduation-cap" class="w-4 h-4 text-temple-red"></i>
<span class="font-semibold text-gray-700">{% trans "GPA" %}</span> <span class="font-semibold text-gray-700">{% trans "GPA" %}</span>
</div> </div>
<span class="text-gray-900 font-medium">{{ applicant.gpa }}</span> <span class="text-gray-900 font-medium">{{ applicant.gpa }}</span>
@ -206,20 +206,20 @@
{# Applications History Tab #} {# Applications History Tab #}
<div id="applications-history" class="tab-content hidden"> <div id="applications-history" class="tab-content hidden">
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2"> <h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="list-checks" class="w-5 h-5 text-kaauh-blue"></i> <i data-lucide="list-checks" class="w-5 h-5 text-temple-red"></i>
{% trans "Application Tracking" %} {% trans "Application Tracking" %}
</h4> </h4>
{% if applications %} {% if applications %}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for application in applications %} {% for application in applications %}
<div class="bg-white rounded-xl border border-gray-200 shadow-sm hover:shadow-md hover:border-kaauh-blue/30 transition-all duration-300"> <div class="bg-white rounded-xl border border-gray-200 shadow-sm hover:shadow-md hover:border-temple-red/30 transition-all duration-300">
<div class="p-5 flex flex-col h-full"> <div class="p-5 flex flex-col h-full">
<!-- Job Title --> <!-- Job Title -->
<div class="mb-3"> <div class="mb-3">
<h5 class="font-bold text-gray-900 mb-2"> <h5 class="font-bold text-gray-900 mb-2">
<a href="{% url 'applicant_application_detail' application.slug %}" <a href="{% url 'applicant_application_detail' application.slug %}"
class="text-kaauh-blue hover:text-kaauh-blue/80 transition"> class="text-temple-red hover:text-temple-red-dark transition">
{{ application.job.title }} {{ application.job.title }}
</a> </a>
</h5> </h5>
@ -233,7 +233,7 @@
<div class="space-y-2 mb-4"> <div class="space-y-2 mb-4">
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<span class="text-xs text-gray-600 font-medium">{% trans "Current Stage" %}</span> <span class="text-xs text-gray-600 font-medium">{% trans "Current Stage" %}</span>
<span class="text-xs font-bold px-2 py-1 rounded-full bg-kaauh-blue text-white"> <span class="text-xs font-bold px-2 py-1 rounded-full bg-temple-red text-white">
{{ application.stage }} {{ application.stage }}
</span> </span>
</div> </div>
@ -250,7 +250,7 @@
<!-- Action Button --> <!-- Action Button -->
<div class="mt-auto"> <div class="mt-auto">
<a href="{% url 'applicant_application_detail' application.slug %}" <a href="{% url 'applicant_application_detail' application.slug %}"
class="inline-flex items-center justify-center gap-2 w-full bg-kaauh-blue hover:bg-[#004f57] text-white font-medium py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md"> class="inline-flex items-center justify-center gap-2 w-full bg-temple-red hover:bg-temple-red-dark text-white font-medium py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
<i data-lucide="eye" class="w-4 h-4"></i> <i data-lucide="eye" class="w-4 h-4"></i>
{% trans "View Details" %} {% trans "View Details" %}
</a> </a>
@ -260,10 +260,10 @@
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}
<div class="bg-kaauh-blue/5 border-2 border-dashed border-kaauh-blue/30 rounded-2xl p-8 text-center"> <div class="bg-temple-red/5 border-2 border-dashed border-temple-red/30 rounded-2xl p-8 text-center">
<i data-lucide="info" class="w-12 h-12 text-kaauh-blue mx-auto mb-4"></i> <i data-lucide="info" class="w-12 h-12 text-temple-red mx-auto mb-4"></i>
<h5 class="mb-3 font-bold text-gray-900">{% trans "You haven't submitted any applications yet." %}</h5> <h5 class="mb-3 font-bold text-gray-900">{% trans "You haven't submitted any applications yet." %}</h5>
<a href="{% url 'kaauh_career' %}" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-6 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md"> <a href="{% url 'kaauh_career' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-red-dark text-white font-medium px-6 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
{% trans "View Available Jobs" %} <i data-lucide="arrow-right" class="w-4 h-4"></i> {% trans "View Available Jobs" %} <i data-lucide="arrow-right" class="w-4 h-4"></i>
</a> </a>
</div> </div>
@ -273,7 +273,7 @@
{# Document Management Tab #} {# Document Management Tab #}
<div id="document-management" class="tab-content hidden"> <div id="document-management" class="tab-content hidden">
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2"> <h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
<i data-lucide="file-text" class="w-5 h-5 text-kaauh-blue"></i> <i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
{% trans "My Uploaded Documents" %} {% trans "My Uploaded Documents" %}
</h4> </h4>
@ -281,7 +281,7 @@
{% trans "You can upload and manage your resume, certificates, and professional documents here. These documents will be attached to your applications." %} {% trans "You can upload and manage your resume, certificates, and professional documents here. These documents will be attached to your applications." %}
</p> </p>
<button type="button" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md mb-6" <button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-red-dark text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md mb-6"
onclick="document.getElementById('documentUploadModal').classList.remove('hidden')"> onclick="document.getElementById('documentUploadModal').classList.remove('hidden')">
<i data-lucide="upload-cloud" class="w-4 h-4"></i> <i data-lucide="upload-cloud" class="w-4 h-4"></i>
{% trans "Upload New Document" %} {% trans "Upload New Document" %}
@ -296,14 +296,14 @@
id="document-{{ document.id }}"> id="document-{{ document.id }}">
<div class="flex-1"> <div class="flex-1">
<div class="flex items-center gap-2 font-semibold text-gray-900"> <div class="flex items-center gap-2 font-semibold text-gray-900">
<i data-lucide="file" class="w-4 h-4 text-kaauh-blue"></i> <i data-lucide="file" class="w-4 h-4 text-temple-red"></i>
<span>{{ document.document_type|title }}</span> <span>{{ document.document_type|title }}</span>
</div> </div>
<span class="text-xs text-gray-500">({{ document.file.name|split:"/"|last }})</span> <span class="text-xs text-gray-500">({{ document.file.name|split:"/"|last }})</span>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<span class="text-xs text-gray-500">{% trans "Uploaded:" %} {{ document.uploaded_at|date:"d M Y" }}</span> <span class="text-xs text-gray-500">{% trans "Uploaded:" %} {{ document.uploaded_at|date:"d M Y" }}</span>
<a href="{{ document.file.url }}" target="_blank" class="text-gray-600 hover:text-kaauh-blue transition"> <a href="{{ document.file.url }}" target="_blank" class="text-gray-600 hover:text-temple-red transition">
<i data-lucide="eye" class="w-4 h-4"></i> <i data-lucide="eye" class="w-4 h-4"></i>
</a> </a>
<button hx-post="{% url 'document_delete' document.pk %}" <button hx-post="{% url 'document_delete' document.pk %}"
@ -356,7 +356,7 @@
// Remove active state from all tabs // Remove active state from all tabs
document.querySelectorAll('.tab-btn').forEach(btn => { document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('border-kaauh-blue', 'text-kaauh-blue', 'bg-kaauh-blue/5'); btn.classList.remove('border-temple-red', 'text-temple-red', 'bg-temple-red/5');
btn.classList.add('border-transparent', 'text-gray-600'); btn.classList.add('border-transparent', 'text-gray-600');
}); });
@ -365,7 +365,7 @@
// Add active state to selected tab // Add active state to selected tab
const activeBtn = document.querySelector(`[data-tab="${tabId}"]`); const activeBtn = document.querySelector(`[data-tab="${tabId}"]`);
activeBtn.classList.add('border-kaauh-blue', 'text-kaauh-blue', 'bg-kaauh-blue/5'); activeBtn.classList.add('border-temple-red', 'text-temple-red', 'bg-temple-red/5');
activeBtn.classList.remove('border-transparent', 'text-gray-600'); activeBtn.classList.remove('border-transparent', 'text-gray-600');
} }

View File

@ -4,177 +4,21 @@
{% block title %}{% trans "Create Account" %}{% endblock %} {% block title %}{% trans "Create Account" %}{% endblock %}
{% block content %} {% block content %}
<style> <div class="min-h-[calc(100vh-80px)] flex justify-center bg-gray-50 py-16 px-5">
/* ---------------------------------------------------------------------- */ <div class="w-full max-w-3xl bg-white rounded-2xl shadow-lg overflow-hidden border border-gray-200">
/* MODERN KAAUH DESIGN SYSTEM */ <!-- Header -->
/* ---------------------------------------------------------------------- */ <div class="bg-gradient-to-r from-temple-red to-[#7a1a29] text-white p-8 text-center">
:root { <h2 class="text-2xl font-bold flex items-center justify-center gap-3">
--kaauh-teal: #00636e; <i data-lucide="user-plus" class="w-7 h-7"></i>
--kaauh-teal-dark: #004a53; {% trans "Create Account" %}
--kaauh-teal-light: #f0f7f8; </h2>
--error-red: #e74c3c;
--border-color: #e2e8f0;
--text-dark: #2d3436;
--text-muted: #636e72;
--shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
--radius: 12px;
}
/* Page Wrapper */
.register-wrapper {
display: flex;
justify-content: center;
padding: 60px 20px;
min-height: calc(100vh - 80px);
background-color: #f8fafc;
}
/* Custom Card */
.kaauh-card {
width: 100%;
max-width: 800px;
background: #ffffff;
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
border: 1px solid var(--border-color);
}
.kaauh-header {
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
color: #ffffff;
padding: 30px;
text-align: center;
}
.kaauh-header h2 {
margin: 0;
font-size: 1.5rem;
font-weight: 700;
letter-spacing: -0.5px;
}
/* Form Layout - Grid System */
.kaauh-body {
padding: 40px;
}
.form-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
gap: 20px;
}
.grid-4 { grid-column: span 4; }
.grid-6 { grid-column: span 6; }
.grid-8 { grid-column: span 8; }
.grid-12 { grid-column: span 12; }
@media (max-width: 768px) {
.grid-4, .grid-6, .grid-8 { grid-column: span 12; }
.kaauh-body { padding: 25px; }
}
/* Pure CSS Form Styling */
.field-group {
display: flex;
flex-direction: column;
}
.field-label {
font-size: 0.9rem;
font-weight: 700;
color: var(--text-dark);
margin-bottom: 8px;
}
.required { color: var(--error-red); }
/* Target Django default inputs */
input, select, textarea {
width: 100%;
padding: 12px 16px;
font-size: 1rem;
border: 2px solid var(--border-color);
border-radius: 8px;
background: #ffffff;
transition: all 0.3s ease;
box-sizing: border-box;
}
input:focus, select:focus {
outline: none;
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 4px rgba(0, 99, 110, 0.1);
}
/* Error Styling */
.error-text {
color: var(--error-red);
font-size: 0.8rem;
font-weight: 600;
margin-top: 5px;
}
/* Button Styling */
.btn-submit {
width: 100%;
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
color: white;
border: none;
padding: 16px;
font-size: 1.1rem;
font-weight: 700;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 20px;
}
.btn-submit:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(0, 99, 110, 0.3);
filter: brightness(1.1);
}
/* Alerts */
.alert {
padding: 15px;
border-radius: 8px;
margin-bottom: 25px;
font-weight: 600;
font-size: 0.9rem;
}
.alert-error, .alert-danger { background: #fee2e2; color: #b91c1c; border: 1px solid #fecaca; }
.alert-success { background: #dcfce7; color: #15803d; border: 1px solid #bbf7d0; }
/* Footer */
.kaauh-footer {
padding: 20px;
background: #f8fafc;
border-top: 1px solid var(--border-color);
text-align: center;
font-size: 0.95rem;
}
.link-teal {
color: var(--kaauh-teal);
font-weight: 700;
text-decoration: none;
}
.link-teal:hover { text-decoration: underline; }
</style>
<div class="register-wrapper">
<div class="kaauh-card">
<div class="kaauh-header">
<h2><i class="fas fa-user-plus"></i> {% trans "Create Account" %}</h2>
</div> </div>
<div class="kaauh-body"> <!-- Body -->
<div class="p-10">
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }}"> <div class="{% if 'error' in message.tags or 'danger' in message.tags %}bg-red-50 border border-red-200 text-red-800{% elif 'success' in message.tags %}bg-green-50 border border-green-200 text-green-800{% else %}bg-blue-50 border border-blue-200 text-blue-800{% endif %} rounded-xl p-4 mb-6 font-semibold text-sm">
{{ message }} {{ message }}
</div> </div>
{% endfor %} {% endfor %}
@ -183,83 +27,83 @@
<form method="post" novalidate> <form method="post" novalidate>
{% csrf_token %} {% csrf_token %}
<div class="form-grid"> <div class="grid grid-cols-1 md:grid-cols-3 gap-5">
<div class="field-group grid-4"> <div class="space-y-2">
<label class="field-label">{% trans "First Name" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "First Name" %} <span class="text-red-500">*</span></label>
{{ form.first_name }} {{ form.first_name }}
{% if form.first_name.errors %}<div class="error-text">{{ form.first_name.errors.0 }}</div>{% endif %} {% if form.first_name.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.first_name.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-4"> <div class="space-y-2">
<label class="field-label">{% trans "Middle Name" %}</label> <label class="block text-sm font-bold text-gray-900">{% trans "Middle Name" %}</label>
{{ form.middle_name }} {{ form.middle_name }}
{% if form.middle_name.errors %}<div class="error-text">{{ form.middle_name.errors.0 }}</div>{% endif %} {% if form.middle_name.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.middle_name.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-4"> <div class="space-y-2">
<label class="field-label">{% trans "Last Name" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "Last Name" %} <span class="text-red-500">*</span></label>
{{ form.last_name }} {{ form.last_name }}
{% if form.last_name.errors %}<div class="error-text">{{ form.last_name.errors.0 }}</div>{% endif %} {% if form.last_name.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.last_name.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-8"> <div class="md:col-span-2 space-y-2">
<label class="field-label">{% trans "Email Address" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "Email Address" %} <span class="text-red-500">*</span></label>
{{ form.email }} {{ form.email }}
{% if form.email.errors %}<div class="error-text">{{ form.email.errors.0 }}</div>{% endif %} {% if form.email.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.email.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-4"> <div class="space-y-2">
<label class="field-label">{% trans "Phone Number" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "Phone Number" %} <span class="text-red-500">*</span></label>
{{ form.phone }} {{ form.phone }}
{% if form.phone.errors %}<div class="error-text">{{ form.phone.errors.0 }}</div>{% endif %} {% if form.phone.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.phone.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-6"> <div class="space-y-2">
<label class="field-label">{% trans "GPA" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "GPA" %} <span class="text-red-500">*</span></label>
{{ form.gpa }} {{ form.gpa }}
{% if form.gpa.errors %}<div class="error-text">{{ form.gpa.errors.0 }}</div>{% endif %} {% if form.gpa.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.gpa.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-6"> <div class="space-y-2">
<label class="field-label">{% trans "National ID / Iqama" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "National ID / Iqama" %} <span class="text-red-500">*</span></label>
{{ form.national_id }} {{ form.national_id }}
{% if form.national_id.errors %}<div class="error-text">{{ form.national_id.errors.0 }}</div>{% endif %} {% if form.national_id.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.national_id.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-6"> <div class="space-y-2">
<label class="field-label">{% trans "Nationality" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "Nationality" %} <span class="text-red-500">*</span></label>
{{ form.nationality }} {{ form.nationality }}
{% if form.nationality.errors %}<div class="error-text">{{ form.nationality.errors.0 }}</div>{% endif %} {% if form.nationality.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.nationality.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-6"> <div class="space-y-2">
<label class="field-label">{% trans "Gender" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "Gender" %} <span class="text-red-500">*</span></label>
{{ form.gender }} {{ form.gender }}
{% if form.gender.errors %}<div class="error-text">{{ form.gender.errors.0 }}</div>{% endif %} {% if form.gender.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.gender.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-6"> <div class="space-y-2">
<label class="field-label">{% trans "Password" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "Password" %} <span class="text-red-500">*</span></label>
{{ form.password }} {{ form.password }}
{% if form.password.errors %}<div class="error-text">{{ form.password.errors.0 }}</div>{% endif %} {% if form.password.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.password.errors.0 }}</div>{% endif %}
</div> </div>
<div class="field-group grid-6"> <div class="space-y-2">
<label class="field-label">{% trans "Confirm Password" %} <span class="required">*</span></label> <label class="block text-sm font-bold text-gray-900">{% trans "Confirm Password" %} <span class="text-red-500">*</span></label>
{{ form.confirm_password }} {{ form.confirm_password }}
{% if form.confirm_password.errors %}<div class="error-text">{{ form.confirm_password.errors.0 }}</div>{% endif %} {% if form.confirm_password.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.confirm_password.errors.0 }}</div>{% endif %}
</div> </div>
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="grid-12"> <div class="md:col-span-3">
<div class="alert alert-danger"> <div class="bg-red-50 border border-red-200 text-red-800 rounded-xl p-4 font-semibold text-sm">
{% for error in form.non_field_errors %}{{ error }}{% endfor %} {% for error in form.non_field_errors %}{{ error }}{% endfor %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="grid-12"> <div class="md:col-span-3">
<button type="submit" class="btn-submit"> <button type="submit" class="w-full bg-gradient-to-r from-temple-red to-[#7a1a29] text-white font-bold text-lg py-4 rounded-xl hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
{% trans "Create Account" %} {% trans "Create Account" %}
</button> </button>
</div> </div>
@ -267,12 +111,39 @@
</form> </form>
</div> </div>
<div class="kaauh-footer"> <!-- Footer -->
<div class="bg-gray-50 border-t border-gray-200 p-5 text-center text-base">
{% trans "Already have an account?" %} {% trans "Already have an account?" %}
<a href="{% url 'account_login' %}?next={% url 'application_submit_form' job.slug %}" class="link-teal"> <a href="{% url 'account_login' %}?next={% url 'application_submit_form' job.slug %}" class="font-bold text-temple-red hover:underline ml-1">
{% trans "Login here" %} {% trans "Login here" %}
</a> </a>
</div> </div>
</div> </div>
</div> </div>
<style>
/* Form input styling to match theme */
input, select, textarea {
width: 100%;
padding: 12px 16px;
font-size: 1rem;
border: 2px solid #e5e7eb;
border-radius: 8px;
background: #ffffff;
transition: all 0.3s ease;
box-sizing: border-box;
}
input:focus, select:focus, textarea:focus {
outline: none;
border-color: #9d2235;
box-shadow: 0 0 0 4px rgba(157, 34, 53, 0.1);
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
lucide.createIcons();
});
</script>
{% endblock %} {% endblock %}

View File

@ -3,202 +3,277 @@
{% block title %}{% trans "Create Application" %} - {{ block.super }}{% endblock %} {% block title %}{% trans "Create Application" %} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* ================================================= */
/* THEME VARIABLES AND GLOBAL STYLES (FROM JOB DETAIL) */
/* ================================================= */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
}
/* Primary Color Overrides */
.text-primary { color: var(--kaauh-teal) !important; }
/* Main Action Button Style */
.btn-main-action, .btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.6rem 1.2rem;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-main-action:hover, .btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Outlined Button Styles */
.btn-secondary, .btn-outline-secondary {
background-color: #f8f9fa;
color: var(--kaauh-teal-dark);
border: 1px solid var(--kaauh-teal);
font-weight: 500;
}
.btn-secondary:hover, .btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Card enhancements */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Colored Header Card */
.candidate-header-card {
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
color: white;
border-radius: 0.75rem 0.75rem 0 0;
padding: 1.5rem;
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
}
.candidate-header-card h1 {
font-weight: 700;
margin: 0;
font-size: 1.8rem;
}
.heroicon {
width: 1.25rem;
height: 1.25rem;
vertical-align: text-bottom;
stroke: currentColor;
margin-right: 0.5rem;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="px-4 py-6">
<!-- Header Card -->
<div class="card mb-4"> <div class="bg-gradient-to-br from-temple-red to-red-800 rounded-xl shadow-xl p-6 mb-6 text-white">
<div class="candidate-header-card"> <div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
<div class="d-flex justify-content-between align-items-start flex-wrap"> <div class="flex-1">
<div class="flex-grow-1"> <h1 class="text-3xl font-bold mb-2 flex items-center gap-2">
<h1 class="h3 mb-1"> <i data-lucide="user-plus" class="w-8 h-8"></i>
<i class="fas fa-user-plus"></i>
{% trans "Create New Application" %} {% trans "Create New Application" %}
</h1> </h1>
<p class="text-white opacity-75 mb-0">{% trans "Enter details to create a new application record." %}</p> <p class="text-red-100 text-lg">{% trans "Enter details to create a new application record." %}</p>
</div> </div>
<div class="d-flex gap-2 mt-1"> <div class="flex gap-2">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#personModal"> <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">
<i class="fas fa-user-plus me-1"></i> <i data-lucide="user-plus" class="w-4 h-4"></i>
<span class="d-none d-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="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}"> <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' %}">
<i class="fas fa-arrow-left"></i> <i data-lucide="arrow-left" class="w-4 h-4"></i>
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span> <span class="hidden sm:inline">{% trans "Back to List" %}</span>
</a> </a>
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="card shadow-sm"> <!-- Form Card -->
<div class="card-header bg-white border-bottom"> <div class="bg-white rounded-xl shadow-md overflow-hidden border border-gray-200">
<h2 class="h5 mb-0 text-primary"> <div class="px-6 py-4 border-b border-gray-200 bg-gray-50">
<i class="fas fa-file-alt me-1"></i> <h2 class="text-xl font-semibold text-temple-dark flex items-center gap-2">
<i data-lucide="file-text" class="w-5 h-5"></i>
{% trans "Application Information" %} {% trans "Application Information" %}
</h2> </h2>
</div> </div>
<div class="card-body"> <div class="p-6">
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
{# Split form into two columns for better horizontal use #} <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="row g-4">
{% for field in form %} {% for field in form %}
<div class="col-md-6"> <div>
{{ field|as_crispy_field }} <label for="{{ field.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ field.label }}
{% if field.field.required %}<span class="text-red-500">*</span>{% endif %}
</label>
{{ field }}
{% if field.help_text %}
<p class="text-sm text-gray-500 mt-1">{{ field.help_text }}</p>
{% endif %}
{% for error in field.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
<hr class="mt-4 mb-4"> <div class="border-t border-gray-200 mt-6 pt-6">
<button class="btn btn-main-action" type="submit"> <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">
<i class="fas fa-save me-1"></i> <i data-lucide="save" class="w-5 h-5"></i>
{% trans "Create Application" %} {% trans "Create Application" %}
</button> </button>
</div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<!-- Modal --> <!-- Modal -->
<div class="modal fade modal-lg" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true"> <div class="hidden fixed inset-0 z-50 overflow-y-auto" id="personModal" role="dialog" aria-labelledby="personModalLabel">
<div class="modal-dialog"> <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="modal-content"> <div class="fixed inset-0 bg-black/50 transition-opacity" aria-hidden="true"></div>
<div class="modal-header"> <span class="hidden sm:inline-block sm:align-middle sm:h-screen sm:align-middle" aria-hidden="true">&#8203;</span>
<h5 class="modal-title" id="personModalLabel"> <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">
<i class="fas fa-question-circle me-2"></i>{% trans "Help" %} <div class="bg-white px-4 pt-5 pb-4 sm:p-6 border-b border-gray-200 flex justify-between items-center">
</h5> <h3 class="text-lg font-semibold text-gray-900 flex items-center gap-2" id="personModalLabel">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <i data-lucide="help-circle" class="w-5 h-5 text-temple-red"></i>
{% trans "Create New Applicant" %}
</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="modal-body"> <div class="px-4 pt-5 pb-4 sm: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="row g-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="col-md-4"> <div>
{{ person_form.first_name|as_crispy_field }} <label for="{{ person_form.first_name.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.first_name.label }}
</label>
{{ person_form.first_name }}
{% for error in person_form.first_name.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
<div class="col-md-4"> <div>
{{ person_form.middle_name|as_crispy_field }} <label for="{{ person_form.middle_name.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.middle_name.label }}
</label>
{{ person_form.middle_name }}
{% for error in person_form.middle_name.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
<div class="col-md-4"> <div>
{{ person_form.last_name|as_crispy_field }} <label for="{{ person_form.last_name.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.last_name.label }}
</label>
{{ person_form.last_name }}
{% for error in person_form.last_name.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
</div> </div>
<div class="row g-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div class="col-md-6"> <div>
{{ person_form.email|as_crispy_field }} <label for="{{ person_form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.email.label }}
</label>
{{ person_form.email }}
{% for error in person_form.email.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
<div class="col-md-6"> <div>
{{ person_form.phone|as_crispy_field }} <label for="{{ person_form.phone.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.phone.label }}
</label>
{{ person_form.phone }}
{% for error in person_form.phone.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
</div> </div>
<div class="row g-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div class="col-md-6"> <div>
{{ person_form.gpa|as_crispy_field }} <label for="{{ person_form.gpa.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.gpa.label }}
</label>
{{ person_form.gpa }}
{% for error in person_form.gpa.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
<div class="col-md-6"> <div>
{{ person_form.national_id|as_crispy_field }} <label for="{{ person_form.national_id.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.national_id.label }}
</label>
{{ person_form.national_id }}
{% for error in person_form.national_id.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
</div> </div>
<div class="row g-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
<div class="col-md-6"> <div>
{{ person_form.date_of_birth|as_crispy_field }} <label for="{{ person_form.date_of_birth.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.date_of_birth.label }}
</label>
{{ person_form.date_of_birth }}
{% for error in person_form.date_of_birth.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
<div class="col-md-6"> <div>
{{ person_form.nationality|as_crispy_field }} <label for="{{ person_form.nationality.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.nationality.label }}
</label>
{{ person_form.nationality }}
{% for error in person_form.nationality.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
</div> </div>
<div class="row g-4"> <div class="mt-4">
<div class="col-12"> <label for="{{ person_form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{{ person_form.address|as_crispy_field }} {{ person_form.address.label }}
</div> </label>
{{ person_form.address }}
{% for error in person_form.address.errors %}
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
{% endfor %}
</div> </div>
</form> </form>
</div> </div>
<div class="modal-footer"> <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button type="submit" class="btn btn-main-action" data-bs-dismiss="modal" form="person_form">{% trans "Save" %}</button> <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">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button> <i data-lucide="save" class="w-4 h-4 mr-2"></i>{% trans "Save" %}
</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">
<i data-lucide="x" class="w-4 h-4 mr-2"></i>{% trans "Close" %}
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block customJS %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
lucide.createIcons();
// Modal functionality
const modal = document.getElementById('personModal');
let isModalOpen = false;
// Open modal buttons
const openModalBtns = document.querySelectorAll('.modal-trigger');
openModalBtns.forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
if (modal) {
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
isModalOpen = true;
setTimeout(() => lucide.createIcons(), 100);
}
});
});
// Close modal buttons
document.querySelectorAll('.modal-close-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
if (modal && isModalOpen) {
modal.classList.add('hidden');
document.body.style.overflow = '';
isModalOpen = false;
}
});
});
// Close modal when clicking outside
if (modal) {
modal.addEventListener('click', function(e) {
if (e.target === modal && isModalOpen) {
modal.classList.add('hidden');
document.body.style.overflow = '';
isModalOpen = false;
}
});
}
// Close modal on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal && isModalOpen) {
modal.classList.add('hidden');
document.body.style.overflow = '';
isModalOpen = false;
}
});
// Add form styling
const formInputs = document.querySelectorAll('input, select, textarea');
formInputs.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');
});
// Reinitialize Lucide icons after HTMX updates
document.body.addEventListener('htmx:afterSwap', function(evt) {
lucide.createIcons();
// Re-apply form styling to new elements
const newFormInputs = evt.detail.xhr.response.querySelectorAll ?
evt.detail.xhr.response.querySelectorAll('input, select, textarea') : [];
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');
});
});
});
</script>
{% endblock %}

View File

@ -3,269 +3,99 @@
{% block title %}{% trans "Delete Application" %} - {{ block.super }}{% endblock %} {% block title %}{% trans "Delete Application" %} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
/* Main Container & Card Styling */
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Warning Section */
.warning-section {
background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%);
border: 1px solid #ffeeba;
border-radius: 0.75rem;
padding: 2rem;
margin-bottom: 2rem;
text-align: center;
}
.warning-icon {
font-size: 4rem;
color: var(--kaauh-warning);
margin-bottom: 1rem;
}
.warning-title {
color: #856404;
font-weight: 700;
margin-bottom: 1rem;
}
.warning-text {
color: #856404;
margin-bottom: 0;
}
/* Info Card */
.app-info {
background-color: #f8f9fa;
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 2rem;
border: 1px solid var(--kaauh-border);
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e9ecef;
}
.info-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.info-icon {
width: 40px;
height: 40px;
background-color: var(--kaauh-teal);
color: white;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
flex-shrink: 0;
}
.info-content {
flex: 1;
}
.info-label {
font-weight: 600;
color: var(--kaauh-primary-text);
margin-bottom: 0.25rem;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.info-value {
color: #6c757d;
font-size: 1rem;
}
/* Button Styling */
.btn-danger {
background-color: var(--kaauh-danger);
border-color: var(--kaauh-danger);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
color: white;
font-weight: 600;
}
/* Consequence List */
.consequence-list {
list-style: none;
padding: 0;
margin: 0;
}
.consequence-list li {
padding: 0.5rem 0;
border-bottom: 1px solid #e9ecef;
color: #6c757d;
}
.consequence-list li:last-child {
border-bottom: none;
}
.consequence-list li i {
color: var(--kaauh-danger);
margin-right: 0.5rem;
}
/* Candidate Info Style */
.candidate-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
border: 3px solid var(--kaauh-teal);
}
.avatar-placeholder {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #e9ecef;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid var(--kaauh-teal);
}
/* Heroicon adjustments to match font-awesome size */
.heroicon {
width: 1.2em;
height: 1.2em;
vertical-align: -0.125em;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container mx-auto px-4 py-8">
<div class="d-flex justify-content-between align-items-center mb-4"> <!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<div> <div>
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="text-3xl font-bold text-temple-red mb-2 flex items-center gap-3">
<i class="fas fa-exclamation-triangle me-2"></i> <div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="alert-triangle" class="w-8 h-8 text-temple-red"></i>
</div>
{% trans "Delete Application" %} {% trans "Delete Application" %}
</h1> </h1>
<p class="text-muted mb-0"> <p class="text-gray-600">
{% trans "You are about to delete an Application record. This action cannot be undone." %} {% trans "You are about to delete an Application record. This action cannot be undone." %}
</p> </p>
</div> </div>
{# Assuming application_detail URL takes object.pk or object.slug #} <a href="{% url 'application_detail' object.pk %}" class="inline-flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-6 py-2.5 rounded-xl transition">
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary"> <i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Application" %}
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Application" %}
</a> </a>
</div> </div>
<div class="row justify-content-center"> <div class="flex justify-center">
<div class="col-lg-8"> <div class="w-full max-w-4xl">
<div class="warning-section"> <!-- Warning Section -->
<div class="warning-icon"> <div class="bg-gradient-to-br from-yellow-100 to-amber-50 border border-yellow-200 rounded-2xl p-8 mb-6 text-center">
<i class="fas fa-exclamation-triangle"></i> <div class="bg-temple-red/10 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-4">
<i data-lucide="alert-triangle" class="w-12 h-12 text-temple-red"></i>
</div> </div>
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3> <h3 class="text-2xl font-bold text-amber-800 mb-3">{% trans "Warning: This action cannot be undone!" %}</h3>
<p class="warning-text"> <p class="text-amber-700">
{% blocktrans with candidate_name=object.candidate.full_name job_title=object.job.title %} {% blocktrans with candidate_name=object.candidate.full_name job_title=object.job.title %}
Deleting the application submitted by **{{ candidate_name }}** for the job **{{ job_title }}** will permanently remove all associated data. Please review the information below carefully before proceeding. Deleting application submitted by <strong>{{ candidate_name }}</strong> for job <strong>{{ job_title }}</strong> will permanently remove all associated data. Please review information below carefully before proceeding.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
</div> </div>
<div class="card kaauh-card mb-4"> <!-- Application Info Card -->
<div class="card-header bg-white border-bottom"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);"> <div class="bg-white border-b border-gray-200 p-5">
<i class="fas fa-file-alt me-2"></i> <h5 class="text-lg font-bold text-temple-red flex items-center gap-2">
<i data-lucide="file-text" class="w-5 h-5"></i>
{% trans "Application to be Deleted" %} {% trans "Application to be Deleted" %}
</h5> </h5>
</div> </div>
<div class="card-body"> <div class="p-6">
<div class="app-info"> <div class="bg-gray-50 rounded-xl p-6 mb-6">
{% if object.candidate %} {% if object.candidate %}
<div class="d-flex align-items-center mb-4"> <div class="flex items-center gap-4 mb-5 pb-5 border-b border-gray-200">
{# Assuming candidate has a profile_image field #}
{% if object.candidate.profile_image %} {% if object.candidate.profile_image %}
<img src="{{ object.candidate.profile_image.url }}" alt="{{ object.candidate.full_name }}" class="candidate-avatar me-3"> <img src="{{ object.candidate.profile_image.url }}" alt="{{ object.candidate.full_name }}" class="w-20 h-20 rounded-full object-cover border-3 border-temple-red">
{% else %} {% else %}
<div class="avatar-placeholder me-3"> <div class="w-20 h-20 rounded-full bg-gray-200 flex items-center justify-center border-3 border-temple-red">
<i class="fas fa-user text-muted fa-2x"></i> <i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
</div> </div>
{% endif %} {% endif %}
<div> <div>
<h4 class="mb-1">{{ object.candidate.full_name }}</h4> <h4 class="text-xl font-bold text-gray-900 mb-1">{{ object.candidate.full_name }}</h4>
{% if object.candidate.email %} {% if object.candidate.email %}
<p class="text-muted mb-0">{{ object.candidate.email }}</p> <p class="text-gray-600">{{ object.candidate.email }}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if object.job %} {% if object.job %}
<div class="info-item"> <div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
<div class="info-icon"> <div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
<i class="fas fa-briefcase"></i> <i data-lucide="briefcase" class="w-5 h-5"></i>
</div> </div>
<div class="info-content"> <div class="flex-1">
<div class="info-label">{% trans "Job Title" %}</div> <div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Job Title" %}</div>
<div class="info-value">{{ object.job.title }}</div> <div class="text-gray-900 text-base">{{ object.job.title }}</div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="info-item"> <div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
<div class="info-icon"> <div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
<i class="fas fa-calendar-alt"></i> <i data-lucide="calendar" class="w-5 h-5"></i>
</div> </div>
<div class="info-content"> <div class="flex-1">
<div class="info-label">{% trans "Applied On" %}</div> <div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Applied On" %}</div>
<div class="info-value">{{ object.created_at|date:"F d, Y \a\t P" }}</div> <div class="text-gray-900 text-base">{{ object.created_at|date:"F d, Y \a\t P" }}</div>
</div> </div>
</div> </div>
{% if object.status %} {% if object.status %}
<div class="info-item"> <div class="flex items-start gap-4">
<div class="info-icon"> <div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
<i class="fas fa-cogs"></i> <i data-lucide="settings" class="w-5 h-5"></i>
</div> </div>
<div class="info-content"> <div class="flex-1">
<div class="info-label">{% trans "Current Status" %}</div> <div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Current Status" %}</div>
<div class="info-value">{{ object.get_status_display }}</div> <div class="text-gray-900 text-base">{{ object.get_status_display }}</div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
@ -273,63 +103,62 @@
</div> </div>
</div> </div>
<div class="card kaauh-card mb-4"> <!-- Consequences Card -->
<div class="card-header bg-white border-bottom"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);"> <div class="bg-white border-b border-gray-200 p-5">
<i class="fas fa-list me-2"></i> <h5 class="text-lg font-bold text-temple-red flex items-center gap-2">
<i data-lucide="list" class="w-5 h-5"></i>
{% trans "What will happen when you delete this Application?" %} {% trans "What will happen when you delete this Application?" %}
</h5> </h5>
</div> </div>
<div class="card-body"> <div class="p-6">
<ul class="consequence-list"> <ul class="space-y-3">
<li> <li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "The Application record and all status history will be permanently deleted." %} {% trans "The Application record and all status history will be permanently deleted." %}
</li> </li>
<li> <li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "All associated screening results, scores, and evaluations will be removed." %} {% trans "All associated screening results, scores, and evaluations will be removed." %}
</li> </li>
<li> <li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "Any linked documents (CV, cover letter) specific to this application will be lost." %} {% trans "Any linked documents (CV, cover letter) specific to this application will be lost." %}
</li> </li>
<li> <li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "The Candidate will still exist, but this specific application history will be lost." %} {% trans "The Candidate will still exist, but this specific application history will be lost." %}
</li> </li>
<li> <li class="flex items-start gap-2 text-gray-700">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "This action cannot be undone under any circumstances." %} {% trans "This action cannot be undone under any circumstances." %}
</li> </li>
</ul> </ul>
</div> </div>
</div> </div>
<div class="card kaauh-card"> <!-- Confirmation Form -->
<div class="card-body"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="p-6">
<form method="post" id="deleteForm"> <form method="post" id="deleteForm">
{% csrf_token %} {% csrf_token %}
<div class="mb-4"> <div class="mb-6">
<div class="form-check"> <label class="flex items-start gap-3 cursor-pointer">
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required> <input type="checkbox" id="confirm_delete" name="confirm_delete" required class="w-5 h-5 mt-0.5 rounded border-gray-300 text-temple-red focus:ring-temple-red focus:ring-offset-0">
<label class="form-check-label" for="confirm_delete"> <span class="text-gray-900 font-medium">
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this application." %}</strong> <strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this application." %}</strong>
</span>
</label> </label>
</div> </div>
</div>
<div class="d-flex justify-content-between"> <div class="flex flex-col sm:flex-row gap-3 justify-between">
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary btn-lg"> <a href="{% url 'application_detail' object.pk %}" class="inline-flex items-center justify-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-8 py-3 rounded-xl transition">
<i class="fas fa-times me-2"></i> <i data-lucide="x" class="w-5 h-5"></i>
{% trans "Cancel" %} {% trans "Cancel" %}
</a> </a>
<button type="submit" <button type="submit" id="deleteButton" disabled class="inline-flex items-center justify-center gap-2 bg-red-500 hover:bg-red-600 text-white font-semibold px-8 py-3 rounded-xl transition shadow-sm hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed">
class="btn btn-danger btn-lg" <i data-lucide="trash-2" class="w-5 h-5"></i>
id="deleteButton"
disabled>
<i class="fas fa-trash me-2"></i>
{% trans "Delete Application Permanently" %} {% trans "Delete Application Permanently" %}
</button> </button>
</div> </div>
@ -349,47 +178,33 @@ document.addEventListener('DOMContentLoaded', function() {
function validateForm() { function validateForm() {
const checkboxChecked = confirmDeleteCheckbox.checked; const checkboxChecked = confirmDeleteCheckbox.checked;
deleteButton.disabled = !checkboxChecked; deleteButton.disabled = !checkboxChecked;
// Toggle button classes for visual feedback
if (checkboxChecked) {
deleteButton.classList.remove('btn-secondary');
deleteButton.classList.add('btn-danger');
} else {
deleteButton.classList.remove('btn-danger');
deleteButton.classList.add('btn-secondary');
}
} }
confirmDeleteCheckbox.addEventListener('change', validateForm); confirmDeleteCheckbox.addEventListener('change', validateForm);
// Initialize state on page load
validateForm(); validateForm();
// Add confirmation and prevent double submission before final submission
deleteForm.addEventListener('submit', function(event) { deleteForm.addEventListener('submit', function(event) {
event.preventDefault(); event.preventDefault();
const candidateName = "{{ object.candidate.full_name|escapejs }}"; const candidateName = "{{ object.candidate.full_name|escapejs }}";
const jobTitle = "{{ object.job.title|escapejs }}"; const jobTitle = "{{ object.job.title|escapejs }}";
// Construct a confirmation message using string replacement for safety const confirmationMessageTemplate = "{% blocktrans with candidate_name='CANDIDATE_PLACEHOLDER' job_title='JOB_PLACEHOLDER' %}Are you absolutely sure you want to permanently delete application by CANDIDATE_PLACEHOLDER for JOB_PLACEHOLDER? This action cannot be reversed!{% endblocktrans %}";
const confirmationMessageTemplate = "{% blocktrans with candidate_name='CANDIDATE_PLACEHOLDER' job_title='JOB_PLACEHOLDER' %}Are you absolutely sure you want to permanently delete the application by CANDIDATE_PLACEHOLDER for JOB_PLACEHOLDER? This action cannot be reversed!{% endblocktrans %}";
const confirmationMessage = confirmationMessageTemplate const confirmationMessage = confirmationMessageTemplate
.replace('CANDIDATE_PLACEHOLDER', candidateName) .replace('CANDIDATE_PLACEHOLDER', candidateName)
.replace('JOB_PLACEHOLDER', jobTitle); .replace('JOB_PLACEHOLDER', jobTitle);
if (confirm(confirmationMessage)) { if (confirm(confirmationMessage)) {
// Disable button and show loading state
deleteButton.disabled = true; deleteButton.disabled = true;
deleteButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>{% trans "Deleting..." %}'; deleteButton.innerHTML = '<svg class="animate-spin w-5 h-5 mr-2" viewBox="0 0 50 50"><circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="5" class="opacity-25"></circle><path fill="none" stroke="currentColor" stroke-width="5" d="M25 5a20 20 0 1 1 0 0 40 20 20 0 1 1 0 0-40" class="opacity-75"></path></svg>{% trans "Deleting..." %}';
// Submit the form programmatically
deleteForm.submit(); deleteForm.submit();
} else { } else {
// If the user cancels the dialog, ensure the button state is reset/validated
validateForm(); validateForm();
} }
}); });
lucide.createIcons();
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -3,211 +3,99 @@
{% block title %}{{ application.name }} - {{ block.super }}{% endblock %} {% block title %}{{ application.name }} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* Card Hover Effects */
.detail-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.detail-card:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
}
/* Tab Button Effects */
.tab-btn {
transition: all 0.2s ease;
}
.tab-btn:hover {
background-color: rgba(157, 34, 53, 0.05);
}
.tab-btn.active {
border-bottom-color: #9d2235;
color: #9d2235;
background-color: #ffffff;
}
/* Button Hover Effects */
.btn-action {
transition: all 0.2s ease;
}
.btn-action:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn-primary {
transition: all 0.2s ease;
}
.btn-primary:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(157, 34, 53, 0.4);
}
/* Timeline Animation */
.timeline-item {
transition: all 0.2s ease;
}
.timeline-item:hover {
transform: translateX(4px);
}
/* Info Card Animation */
.info-card {
transition: all 0.2s ease;
}
.info-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
/* Progress Bar Animation */
.progress-bar {
transition: width 0.5s ease;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <div class="px-4 py-6" id="application-detail-content">
<nav aria-label="breadcrumb" class="mb-6">
{# Breadcrumb #} <ol class="flex items-center space-x-2 text-sm">
<nav class="mb-6" aria-label="breadcrumb"> <li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition">Home</a></li>
<ol class="flex items-center gap-2 text-sm">
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Home" %}
</a></li>
<li class="text-gray-400">/</li> <li class="text-gray-400">/</li>
<li><a href="{% url 'job_detail' application.job.slug %}" class="text-gray-500 hover:text-temple-red transition"> <li><a href="{% url 'job_detail' application.job.slug %}" class="text-gray-500 hover:text-temple-red transition">Job: {{application.job.title}}</a></li>
{% trans "Job:" %} ({{ application.job.title }})
</a></li>
<li class="text-gray-400">/</li> <li class="text-gray-400">/</li>
<li class="text-temple-red font-semibold" aria-current="page">{% trans "Application Detail" %}</li> <li class="font-semibold text-temple-red" aria-current="page">{% trans "Applicant Detail" %}</li>
</ol> </ol>
</nav> </nav>
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
{# LEFT COLUMN: MAIN application DETAILS AND TABS #}
{# LEFT COLUMN: MAIN DETAILS AND TABS #} <div class="lg:col-span-2">
<div class="lg:col-span-8"> <div class="bg-white rounded-xl shadow-md overflow-hidden border border-gray-200">
<div class="detail-card bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
{# HEADER SECTION #} {# HEADER SECTION #}
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-6"> <div class="bg-gradient-to-br from-temple-red to-red-800 text-white p-6">
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4"> <div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
<div class="flex-1"> <div>
<h1 class="text-2xl sm:text-3xl font-bold mb-2">{{ application.name }}</h1> <h1 class="text-3xl font-extrabold mb-2">{{ application.name }}</h1>
<div class="flex items-center gap-2 mb-2"> <div class="flex items-center gap-2 mb-2">
<span class="inline-block text-xs font-bold uppercase tracking-wide px-3 py-1.5 rounded-full bg-white/20 backdrop-blur-sm"> <span id="stageDisplay" class="bg-white/20 backdrop-blur-sm px-3 py-1 rounded-full text-sm font-medium">
{% trans "Stage:" %} <span class="font-normal">{{ application.stage }}</span> {% trans "Stage:" %}
<span class="font-bold">{{ application.stage }}</span>
</span> </span>
</div> </div>
<p class="text-sm text-white/80"> <p class="text-red-100 text-sm">
{% trans "Applied for:" %} <strong class="text-white">{{ application.job.title }}</strong> {% trans "Applied for:" %} <strong>{{ application.job.title }}</strong>
</p> </p>
</div> </div>
{# Change Stage button #} {# Change Stage button #}
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %} {% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
<button type="button" <button type="button" class="stage-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="stageUpdateModal">
class="btn-primary inline-flex items-center gap-2 bg-white/10 hover:bg-white/20 text-white font-medium px-4 py-2 rounded-xl text-sm transition" <i data-lucide="repeat" class="w-4 h-4"></i> {% trans "Change Stage" %}
onclick="document.getElementById('stageUpdateModal').classList.remove('hidden')">
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
{% trans "Change Stage" %}
</button> </button>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{# TABS NAVIGATION #} {# LEFT TABS NAVIGATION #}
<div class="border-b border-gray-200 bg-gray-50 overflow-x-auto"> <div class="bg-gray-50 border-b border-gray-200 px-6">
<div class="flex min-w-max px-4 gap-1"> <ul class="flex space-x-1" id="candidateTabs" role="tablist">
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition" <li class="role-presentation">
onclick="showTab('contact-pane')" <button class="tab-btn px-4 py-3 text-sm font-medium border-b-2 border-transparent hover:text-temple-red transition flex items-center gap-2 active text-temple-dark border-b-temple-red bg-white" id="contact-tab" data-tab="contact-pane" type="button" role="tab" aria-controls="contact-pane" aria-selected="true">
data-tab="contact-pane"> <i data-lucide="id-card" class="w-4 h-4"></i> {% trans "Contact & Job" %}
<i data-lucide="id-card" class="w-4 h-4 mr-2 inline"></i>
{% trans "Contact & Job" %}
</button> </button>
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition" </li>
onclick="showTab('timeline-pane')"
data-tab="timeline-pane"> <li class="role-presentation">
<i data-lucide="route" class="w-4 h-4 mr-2 inline"></i> <button class="tab-btn px-4 py-3 text-sm font-medium text-gray-600 border-b-2 border-transparent hover:text-temple-red transition flex items-center gap-2" id="timeline-tab" data-tab="timeline-pane" type="button" role="tab" aria-controls="timeline-pane" aria-selected="false">
{% trans "Journey Timeline" %} <i data-lucide="route" class="w-4 h-4"></i> {% trans "Journey Timeline" %}
</button> </button>
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition" </li>
onclick="showTab('documents-pane')"
data-tab="documents-pane"> <li class="role-presentation">
<i data-lucide="file-text" class="w-4 h-4 mr-2 inline"></i> <button class="tab-btn px-4 py-3 text-sm font-medium text-gray-600 border-b-2 border-transparent hover:text-temple-red transition flex items-center gap-2" id="documents-tab" data-tab="documents-pane" type="button" role="tab" aria-controls="documents-pane" aria-selected="false">
{% trans "Documents" %} <i data-lucide="file-text" class="w-4 h-4"></i> {% trans "Documents" %}
</button> </button>
{% if application.parsed_summary %} </li>
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition" </ul>
onclick="showTab('summary-pane')"
data-tab="summary-pane">
<i data-lucide="sparkles" class="w-4 h-4 mr-2 inline"></i>
{% trans "AI Summary" %}
</button>
{% endif %}
{% if application.is_resume_parsed %}
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
onclick="showTab('analysis-pane')"
data-tab="analysis-pane">
<i data-lucide="bar-chart-3" class="w-4 h-4 mr-2 inline"></i>
{% trans "AI Analysis" %}
</button>
{% endif %}
</div>
</div> </div>
<div class="p-6"> <div class="p-6">
<div class="tab-content" id="candidateTabsContent">
{# TAB 1: CONTACT & DATES #} {# TAB 1 CONTENT: CONTACT & DATES #}
<div id="contact-pane" class="tab-content"> <div class="tab-pane block" id="contact-pane" role="tabpanel" aria-labelledby="contact-tab">
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2"> <h5 class="text-lg font-bold text-temple-red mb-4">{% trans "Core Details" %}</h5>
<i data-lucide="info" class="w-5 h-5"></i>
{% trans "Core Details" %}
</h5>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200"> <div class="flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
<div class="flex items-center gap-3"> <i data-lucide="mail" class="w-8 h-8 text-gray-400 shrink-0"></i>
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="mail" class="w-6 h-6 text-temple-red"></i>
</div>
<div> <div>
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Email" %}</p> <p class="text-xs text-gray-500">{% trans "Email" %}</p>
<p class="font-semibold text-gray-900">{{ application.email }}</p> <p class="font-semibold text-gray-800">{{ application.email }}</p>
</div> </div>
</div> </div>
</div> <div class="flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200"> <i data-lucide="briefcase" class="w-8 h-8 text-gray-400 shrink-0"></i>
<div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
<i data-lucide="briefcase" class="w-6 h-6 text-temple-red"></i>
</div>
<div> <div>
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Position Applied" %}</p> <p class="text-xs text-gray-500">{% trans "Position Applied" %}</p>
<p class="font-semibold text-gray-900">{{ application.job.title }}</p> <p class="font-semibold text-gray-800">{{ application.job.title }}</p>
</div> </div>
</div> </div>
</div> <div class="md:col-span-2 flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200 md:col-span-2"> <i data-lucide="calendar-check" class="w-8 h-8 text-gray-400 shrink-0"></i>
<div class="flex-1">
<p class="text-xs text-gray-500">{% trans "Applied Date" %}</p>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center"> <p class="font-semibold text-gray-800">{{ application.created_at|date:"M d, Y H:i" }}</p>
<i data-lucide="calendar-check" class="w-6 h-6 text-temple-red"></i> <span class="bg-gray-200 text-gray-700 px-2 py-1 rounded text-xs font-medium">
</div> <i data-lucide="clock" class="inline w-3 h-3 mr-1"></i>
<div>
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Applied Date" %}</p>
<div class="flex items-center gap-2 flex-wrap">
<p class="font-semibold text-gray-900">{{ application.created_at|date:"M d, Y H:i" }}</p>
<span class="inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full bg-temple-red/10 text-temple-red">
<i data-lucide="clock" class="w-3 h-3"></i>
{{ application.created_at|naturaltime }} {{ application.created_at|naturaltime }}
</span> </span>
</div> </div>
@ -215,108 +103,97 @@
</div> </div>
</div> </div>
</div> </div>
</div>
{# TAB 2: JOURNEY TIMELINE #} {# TAB 2 CONTENT: TIMELINE #}
<div id="timeline-pane" class="tab-content hidden"> <div class="tab-pane hidden" id="timeline-pane" role="tabpanel" aria-labelledby="timeline-tab">
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden"> <div class="bg-white border border-gray-200 rounded-xl">
<div class="p-4 border-b border-gray-200"> <div class="p-4 border-b border-gray-200">
<h5 class="text-sm font-bold text-gray-600 flex items-center gap-2"> <h5 class="text-sm font-semibold text-gray-600 flex items-center gap-2">
<i data-lucide="route" class="w-5 h-5"></i> <i data-lucide="route" class="w-4 h-4"></i>{% trans "Application Journey" %}
{% trans "Application Journey" %}
</h5> </h5>
</div> </div>
<div class="p-6"> <div class="p-6">
<h6 class="text-xs uppercase tracking-wider text-gray-500 font-bold mb-3">{% trans "Current Stage" %}</h6>
<p class="text-xs font-bold text-gray-500 uppercase tracking-wide mb-4">{% trans "Current Stage" %}</p> <div class="p-4 mb-4 rounded-lg bg-red-50 border border-red-200">
<div class="bg-temple-red/5 border border-temple-red/30 rounded-xl p-4 mb-6"> <p class="font-bold text-lg text-temple-dark mb-1">{{ application.stage }}</p>
<p class="text-xl font-bold text-temple-red mb-1">{{ application.stage }}</p>
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">
{% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }} {% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }}
</p> </p>
</div> </div>
<p class="text-xs font-bold text-gray-500 uppercase tracking-wide mb-4 pt-4 border-t border-gray-200">{% trans "Historical Timeline" %}</p> <h6 class="text-xs uppercase tracking-wider text-gray-500 font-bold mb-3 pt-4 border-t border-gray-200">{% trans "Historical Timeline" %}</h6>
<div class="relative pl-8 border-l-2 border-gray-200 space-y-6"> <div class="relative pl-8">
<div class="absolute left-3.5 top-0 bottom-0 w-0.5 bg-gray-200"></div>
{# Application Submitted #} <div class="relative mb-6">
<div class="timeline-item relative"> <div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-temple-red flex items-center justify-center text-white border-4 border-white shadow-lg"> <i data-lucide="file-signature" class="w-4 h-4"></i>
<i data-lucide="file-signature" class="w-3 h-3"></i>
</div> </div>
<div class="ml-4"> <div class="ml-12">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Application Submitted" %}</p> <p class="font-semibold text-gray-800">{% trans "Application Submitted" %}</p>
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i> <i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.created_at|date:"M d, Y" }}
{{ application.created_at|date:"M d, Y" }}
<span class="mx-2">|</span> <span class="mx-2">|</span>
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i> <i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.created_at|date:"h:i A" }}
{{ application.created_at|date:"h:i A" }}
</p> </p>
</div> </div>
</div> </div>
{% if application.exam_date %} {% if application.exam_date %}
<div class="timeline-item relative"> <div class="relative mb-6">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-temple-red flex items-center justify-center text-white border-4 border-white shadow-lg"> <div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
<i data-lucide="clipboard-check" class="w-3 h-3"></i> <i data-lucide="clipboard-check" class="w-4 h-4"></i>
</div> </div>
<div class="ml-4"> <div class="ml-12">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Exam" %}</p> <p class="font-semibold text-gray-800">{% trans "Exam" %}</p>
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i> <i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.exam_date|date:"M d, Y" }}
{{ application.exam_date|date:"M d, Y" }}
<span class="mx-2">|</span> <span class="mx-2">|</span>
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i> <i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.exam_date|date:"h:i A" }}
{{ application.exam_date|date:"h:i A" }}
</p> </p>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if application.get_interview_date %} {% if application.get_interview_date %}
<div class="timeline-item relative"> <div class="relative mb-6">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-amber-500 flex items-center justify-center text-white border-4 border-white shadow-lg"> <div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
<i data-lucide="message-circle" class="w-3 h-3"></i> <i data-lucide="message-circle" class="w-4 h-4"></i>
</div> </div>
<div class="ml-4"> <div class="ml-12">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Interview" %}</p> <p class="font-semibold text-gray-800">{% trans "Interview" %}</p>
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i> <i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.get_interview_date}}
{{ application.get_interview_date }}
<span class="mx-2">|</span> <span class="mx-2">|</span>
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i> <i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.get_interview_time}}
{{ application.get_interview_time }}
</p> </p>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if application.offer_date %} {% if application.offer_date %}
<div class="timeline-item relative"> <div class="relative mb-6">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-emerald-500 flex items-center justify-center text-white border-4 border-white shadow-lg"> <div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
<i data-lucide="handshake" class="w-3 h-3"></i> <i data-lucide="handshake" class="w-4 h-4"></i>
</div> </div>
<div class="ml-4"> <div class="ml-12">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Offer" %}</p> <p class="font-semibold text-gray-800">{% trans "Offer" %}</p>
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i> <i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.offer_date|date:"M d, Y" }}
{{ application.offer_date|date:"M d, Y" }}
</p> </p>
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if application.hired_date %} {% if application.hired_date %}
<div class="timeline-item relative"> <div class="relative mb-6">
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-emerald-600 flex items-center justify-center text-white border-4 border-white shadow-lg"> <div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
<i data-lucide="user-check" class="w-3 h-3"></i> <i data-lucide="check-circle" class="w-4 h-4"></i>
</div> </div>
<div class="ml-4"> <div class="ml-12">
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Hired" %}</p> <p class="font-semibold text-gray-800">{% trans "Hired" %}</p>
<p class="text-xs text-gray-500"> <p class="text-xs text-gray-500">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i> <i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.hired_date|date:"M d, Y" }}
{{ application.hired_date|date:"M d, Y" }}
</p> </p>
</div> </div>
</div> </div>
@ -326,158 +203,31 @@
</div> </div>
</div> </div>
{# TAB 3: DOCUMENTS #} {# TAB 3 CONTENT: DOCUMENTS #}
<div id="documents-pane" class="tab-content hidden"> <div class="tab-pane hidden" id="documents-pane" role="tabpanel" aria-labelledby="documents-tab">
{% with documents=application.documents %} {% with documents=application.documents %}
{% include 'includes/document_list.html' %} {% include 'includes/document_list.html' %}
{% endwith %} {% endwith %}
</div> </div>
</div>
</div>
</div>
</div>
{# TAB 4: AI SUMMARY #} {# RIGHT COLUMN: ACTIONS AND TIMING #}
{% if application.parsed_summary %} <div class="space-y-6">
<div id="summary-pane" class="tab-content hidden"> {# ACTIONS CARD #}
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2"> <div class="bg-white rounded-xl shadow-md p-6 border border-gray-200">
<i data-lucide="sparkles" class="w-5 h-5"></i> <h5 class="text-sm font-semibold text-gray-600 mb-4 flex items-center gap-2">
{% trans "AI Generated Summary" %} <i data-lucide="settings" class="w-4 h-4"></i>{% trans "Management Actions" %}
</h5> </h5>
<div class="bg-gray-50 rounded-xl p-4 border-l-4 border-temple-red"> <div class="space-y-3">
{% include 'includes/application_modal_body.html' %} <a href="{% url 'application_list' %}" class="w-full flex items-center justify-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 py-3 px-4 rounded-lg text-sm font-medium transition">
</div>
</div>
{% endif %}
{# TAB 5: AI ANALYSIS #}
{% if application.is_resume_parsed %}
<div id="analysis-pane" class="tab-content hidden">
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
<i data-lucide="bar-chart-3" class="w-5 h-5"></i>
{% trans "AI Analysis Report" %}
</h5>
<div class="bg-gray-50 rounded-xl p-4 border-l-4 border-temple-red">
{% with analysis=application.ai_analysis_data %}
{# Match Score Card #}
<div class="bg-white rounded-xl p-4 mb-4 shadow-sm">
<div class="flex justify-between items-center mb-3">
<h6 class="font-bold text-gray-900">{% trans "Match Score" %}</h6>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-bold {% if analysis.match_score >= 70 %}bg-emerald-500 text-white{% elif analysis.match_score >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
{{ analysis.match_score }}%
</span>
</div>
<div class="h-2 bg-gray-200 rounded-full overflow-hidden">
<div class="progress-bar h-full {% if analysis.match_score >= 70 %}bg-emerald-500{% elif analysis.match_score >= 40 %}bg-amber-500{% else %}bg-red-500{% endif %}"
style="width: {{ analysis.match_score }}%"></div>
</div>
</div>
{# Category & Job Fit #}
<div class="mb-4">
<h6 class="font-bold text-temple-red mb-2">{% trans "Category" %}</h6>
<p class="text-gray-700 mb-3">{{ analysis.category }}</p>
<h6 class="font-bold text-temple-red mb-2">{% trans "Job Fit Narrative" %}</h6>
<p class="text-gray-700">{{ analysis.job_fit_narrative }}</p>
</div>
{# Strengths and Weaknesses #}
<div class="mb-4">
<h6 class="font-bold text-temple-red mb-2">{% trans "Strengths" %}</h6>
<p class="text-emerald-600 mb-3">{{ analysis.strengths }}</p>
<h6 class="font-bold text-temple-red mb-2">{% trans "Weaknesses" %}</h6>
<p class="text-red-600">{{ analysis.weaknesses }}</p>
</div>
{# Recommendation #}
<div class="bg-temple-red/5 rounded-xl p-4 mb-4 border border-temple-red/20">
<h6 class="font-bold text-temple-red mb-2">{% trans "Recommendation" %}</h6>
<p class="text-gray-700">{{ analysis.recommendation }}</p>
</div>
{# Top Keywords #}
<div class="mb-4">
<h6 class="font-bold text-temple-red mb-2">{% trans "Top Keywords" %}</h6>
<div class="flex flex-wrap gap-2">
{% for keyword in analysis.top_3_keywords %}
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-temple-red/10 text-temple-red">
{{ keyword }}
</span>
{% endfor %}
</div>
</div>
{# Professional Details #}
<div class="bg-white rounded-xl p-4 shadow-sm mb-4">
<h6 class="font-bold text-temple-red mb-3">{% trans "Professional Details" %}</h6>
<div class="space-y-2">
<p class="flex justify-between"><span class="text-gray-600">{% trans "Years of Experience:" %}</span> <strong>{{ analysis.years_of_experience }}</strong></p>
<p class="flex justify-between"><span class="text-gray-600">{% trans "Most Recent Job Title:" %}</span> <strong>{{ analysis.most_recent_job_title }}</strong></p>
<p class="flex justify-between">
<span class="text-gray-600">{% trans "Experience Industry Match:" %}</span>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-bold {% if analysis.experience_industry_match >= 70 %}bg-emerald-500 text-white{% elif analysis.experience_industry_match >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
{{ analysis.experience_industry_match }}%
</span>
</p>
<p class="flex justify-between"><span class="text-gray-600">{% trans "Soft Skills Score:" %}</span> <strong>{{ analysis.soft_skills_score }}%</strong></p>
</div>
</div>
{# Criteria Checklist #}
<div class="mb-4">
<h6 class="font-bold text-temple-red mb-2">{% trans "Criteria Assessment" %}</h6>
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-gray-200">
<th class="text-left py-2 px-3 font-semibold text-gray-700">{% trans "Criteria" %}</th>
<th class="text-left py-2 px-3 font-semibold text-gray-700">{% trans "Status" %}</th>
</tr>
</thead>
<tbody>
{% for criterion, status in analysis.criteria_checklist.items %}
<tr class="border-b border-gray-100">
<td class="py-2 px-3 text-gray-700">{{ criterion }}</td>
<td class="py-2 px-3">
{% if status == "Met" %}
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-700">{% trans "Met" %}</span>
{% elif status == "Not Met" %}
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-red-100 text-red-700">{% trans "Not Met" %}</span>
{% else %}
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-gray-100 text-gray-700">{{ status }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endwith %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
{# RIGHT COLUMN: ACTIONS #}
<div class="lg:col-span-4 space-y-4">
{# Management Actions #}
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
<h5 class="text-sm font-bold text-gray-600 mb-4 flex items-center gap-2">
<i data-lucide="settings" class="w-5 h-5"></i>
{% trans "Management Actions" %}
</h5>
<div class="space-y-2">
<a href="{% url 'application_list' %}"
class="btn-action inline-flex items-center gap-2 w-full bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i> <i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Back to List" %} {% trans "Back to List" %}
</a> </a>
{% if application.resume %} {% if application.resume %}
<a href="{{ application.resume.url }}" download <a href="{{ application.resume.url }}" download class="w-full flex items-center justify-center gap-2 bg-temple-red hover:bg-red-800 text-white py-3 px-4 rounded-lg text-sm font-medium transition shadow-sm">
class="btn-primary inline-flex items-center gap-2 w-full bg-temple-red hover:bg-[#8a1e2f] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-lg hover:shadow-xl">
<i data-lucide="download" class="w-4 h-4"></i> <i data-lucide="download" class="w-4 h-4"></i>
{% trans "Download Resume" %} {% trans "Download Resume" %}
</a> </a>
@ -485,102 +235,263 @@
</div> </div>
</div> </div>
{# Time to Hire #} {# TIME TO HIRE CARD #}
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4"> <div class="bg-white rounded-xl shadow-md p-6 border border-gray-200">
<h5 class="text-sm font-bold text-gray-600 mb-3 flex items-center gap-2"> <h5 class="text-sm font-semibold text-gray-600 mb-4 flex items-center gap-2">
<i data-lucide="clock" class="w-5 h-5"></i> <i data-lucide="clock" class="w-4 h-4"></i>{% trans "Time to Hire:" %}
{% trans "Time to Hire:" %}
</h5> </h5>
<div class="text-center">
{% with days=application.time_to_hire_days %} {% with days=application.time_to_hire_days %}
<p class="text-2xl font-bold text-temple-red"> {% if days > 0 %}
{% if days > 0 %}{{ days }} day{{ days|pluralize }}{% else %}0{% endif %} <p class="text-2xl font-bold text-temple-dark">{{ days }}</p>
</p> <p class="text-sm text-gray-500">day{{ days|pluralize }}</p>
{% else %}
<p class="text-2xl font-bold text-temple-dark">0</p>
<p class="text-sm text-gray-500">days</p>
{% endif %}
{% endwith %} {% endwith %}
</div> </div>
</div>
</div>
</div>
</div>
{# Resume Parsing Section #}
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
<h5 class="text-sm font-bold text-gray-600 mb-3 flex items-center gap-2">
<i data-lucide="file-text" class="w-5 h-5"></i>
{% trans "Resume Analysis" %}
</h5>
<div class="resume-parsed-section"> <div class="resume-parsed-section">
{% if application.is_resume_parsed %} {% if application.is_resume_parsed %}
{% include 'recruitment/application_resume_template.html' %} {% include 'recruitment/application_resume_template.html' %}
{% else %} {% else %}
{% if application.scoring_timeout %} {% if application.scoring_timeout %}
<div class="flex flex-col items-center justify-center py-6"> <div class="flex justify-center items-center py-12">
<div class="flex items-center gap-2 text-temple-red"> <div class="flex items-center gap-3 text-temple-red">
<i data-lucide="bot" class="w-6 h-6 animate-pulse"></i> <i data-lucide="robot" class="w-8 h-8 animate-pulse"></i>
<span class="text-sm font-medium">{% trans "Resume Analysis In Progress..." %}</span> <span class="text-sm">{% trans "Resume Analysis In Progress..." %}</span>
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="flex flex-col items-center justify-center py-6"> <div class="flex justify-center items-center py-12">
<button type="submit" <button type="submit" class="flex items-center gap-2 bg-temple-red hover:bg-red-800 text-white py-3 px-6 rounded-lg text-sm font-medium transition shadow-sm" hx-get="{% url 'application_retry_scoring' application.slug %}" hx-select=".resume-parsed-section" hx-target=".resume-parsed-section" hx-swap="outerHTML" hx-on:click="this.disabled=true;this.innerHTML=`Scoring Resume, Please Wait... <i data-lucide='loader-2' class='w-4 h-4 animate-spin'></i>`">
class="btn-primary inline-flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-lg hover:shadow-xl"
hx-get="{% url 'application_retry_scoring' application.slug %}"
hx-select=".resume-parsed-section"
hx-target=".resume-parsed-section"
hx-swap="outerHTML"
hx-on:click="this.disabled=true;this.innerHTML=`Scoring Resume, Please Wait...`">
<i data-lucide="refresh-cw" class="w-4 h-4"></i> <i data-lucide="refresh-cw" class="w-4 h-4"></i>
{% trans "Unable to Parse Resume, Click to Retry" %} {% trans "Unable to Parse Resume, click to retry" %}
</button> </button>
</div> </div>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
</div>
</div>
</div>
</div>
{# Stage Update Modal for Staff Users #} {# STAGE UPDATE MODAL INCLUDED FOR STAFF USERS #}
{% if user.is_staff %} {% if user.is_staff %}
<div id="stageUpdateModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-2xl shadow-xl max-w-lg w-full">
{% include "recruitment/partials/stage_update_modal.html" with application=application form=stage_form %} {% include "recruitment/partials/stage_update_modal.html" with application=application form=stage_form %}
</div>
</div>
{% endif %} {% endif %}
{% endblock %}
{% block customJS %}
<script> <script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
lucide.createIcons(); lucide.createIcons();
// Tab switching functionality // ========================================
function showTab(tabId) { // Tab Navigation Functionality
// Hide all tab contents // ========================================
document.querySelectorAll('.tab-content').forEach(content => { const tabButtons = document.querySelectorAll('.tab-btn');
content.classList.add('hidden'); const tabPanes = document.querySelectorAll('.tab-pane');
tabButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault();
// Get the target tab pane ID
const targetTabId = this.getAttribute('data-tab');
// Remove active state from all buttons
tabButtons.forEach(btn => {
btn.classList.remove('active', 'text-temple-dark', 'border-b-temple-red', 'bg-white');
btn.classList.add('text-gray-600', 'border-transparent');
btn.setAttribute('aria-selected', 'false');
}); });
// Remove active state from all tabs // Add active state to clicked button
document.querySelectorAll('.tab-btn').forEach(btn => { this.classList.add('active', 'text-temple-dark', 'border-b-temple-red', 'bg-white');
btn.classList.remove('active'); this.classList.remove('text-gray-600', 'border-transparent');
btn.classList.remove('border-temple-red', 'text-temple-red', 'bg-white'); this.setAttribute('aria-selected', 'true');
btn.classList.add('border-transparent', 'text-gray-600');
// Hide all tab panes
tabPanes.forEach(pane => {
pane.classList.add('hidden');
pane.classList.remove('block');
}); });
// Show selected tab content // Show the target tab pane
document.getElementById(tabId).classList.remove('hidden'); const targetPane = document.getElementById(targetTabId);
if (targetPane) {
targetPane.classList.remove('hidden');
targetPane.classList.add('block');
}
});
});
// Add active state to selected tab // ========================================
const activeBtn = document.querySelector(`[data-tab="${tabId}"]`); // Document Upload Modal Functionality
activeBtn.classList.add('active', 'border-temple-red', 'text-temple-red', 'bg-white'); // ========================================
activeBtn.classList.remove('border-transparent', 'text-gray-600');
const uploadModal = document.getElementById('documentUploadModal');
let isModalOpen = false;
// Open modal buttons
const openModalBtns = document.querySelectorAll('.upload-modal-trigger');
openModalBtns.forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
if (uploadModal) {
uploadModal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
isModalOpen = true;
// Initialize Lucide icons inside modal
setTimeout(() => lucide.createIcons(), 100);
}
});
});
// Close modal buttons (both close X and Cancel button)
document.querySelectorAll('.modal-close-btn, .modal-cancel-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
if (uploadModal && isModalOpen) {
uploadModal.classList.add('hidden');
document.body.style.overflow = '';
isModalOpen = false;
}
});
});
// Close modal when clicking outside
if (uploadModal) {
uploadModal.addEventListener('click', function(e) {
if (e.target === uploadModal && isModalOpen) {
uploadModal.classList.add('hidden');
document.body.style.overflow = '';
isModalOpen = false;
}
});
} }
// Initialize first tab as active // Close modal on escape key
showTab('contact-pane'); document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && uploadModal && isModalOpen) {
// Modal close functionality uploadModal.classList.add('hidden');
document.addEventListener('click', function(e) { document.body.style.overflow = '';
const modal = document.getElementById('stageUpdateModal'); isModalOpen = false;
if (e.target === modal) {
modal.classList.add('hidden');
} }
}); });
// ========================================
// Stage Update Modal Functionality
// ========================================
const stageModal = document.getElementById('stageUpdateModal');
let isStageModalOpen = false;
const currentStage = '{{ application.stage }}';
// Open stage modal buttons
const openStageModalBtns = document.querySelectorAll('.stage-modal-trigger');
openStageModalBtns.forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
if (stageModal) {
stageModal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
isStageModalOpen = true;
// Initialize Lucide icons inside modal
setTimeout(() => lucide.createIcons(), 100);
// Set initial stage selection
const stageSelect = document.getElementById('id_stage');
if (stageSelect) {
stageSelect.value = currentStage;
// Call updateStageInfo if function exists
if (typeof updateStageInfo === 'function') {
updateStageInfo(currentStage, currentStage);
}
}
}
});
});
// Stage selection change handler
const stageSelect = document.getElementById('id_stage');
if (stageSelect) {
stageSelect.addEventListener('change', function(e) {
const selectedValue = e.target.value;
if (typeof updateStageInfo === 'function') {
updateStageInfo(selectedValue, currentStage);
}
});
}
// Close stage modal buttons
document.querySelectorAll('#stageUpdateModal button').forEach(btn => {
btn.addEventListener('click', function(e) {
e.preventDefault();
if (stageModal && isStageModalOpen) {
stageModal.classList.add('hidden');
document.body.style.overflow = '';
isStageModalOpen = false;
}
});
});
// Close stage modal when clicking outside
if (stageModal) {
stageModal.addEventListener('click', function(e) {
if (e.target === stageModal || (e.target.classList.contains('fixed') && e.target.classList.contains('inset-0'))) {
stageModal.classList.add('hidden');
document.body.style.overflow = '';
isStageModalOpen = false;
}
});
}
// Close stage modal on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && stageModal && isStageModalOpen) {
stageModal.classList.add('hidden');
document.body.style.overflow = '';
isStageModalOpen = false;
}
});
// Close modal after successful HTMX form submission
document.body.addEventListener('htmx:afterRequest', function(evt) {
// Close document upload modal
if (evt.detail.successful && uploadModal && isModalOpen) {
const form = evt.detail.elt;
if (form && form.tagName === 'FORM' && form.action.includes('document_upload')) {
uploadModal.classList.add('hidden');
document.body.style.overflow = '';
isModalOpen = false;
}
}
// Close stage update modal
if (evt.detail.successful && stageModal && isStageModalOpen) {
const form = evt.detail.elt;
if (form && form.tagName === 'FORM' && form.action.includes('application_update_stage')) {
stageModal.classList.add('hidden');
document.body.style.overflow = '';
isStageModalOpen = false;
// Reload page to update stage display
window.location.reload();
}
}
});
// ========================================
// Reinitialize Lucide icons after HTMX updates
// ========================================
document.body.addEventListener('htmx:afterSwap', function(evt) {
lucide.createIcons();
});
});
</script> </script>
{% endblock %} {% endblock %}

File diff suppressed because it is too large Load Diff

View File

@ -3,229 +3,74 @@
{% block title %}{% trans "Update" %} {{ object.name }} - {{ block.super }}{% endblock %} {% block title %}{% trans "Update" %} {{ object.name }} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* UI Variables for the KAAT-S Theme */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-gray-light: #f8f9fa;
}
/* Form Container Styling */
.form-container {
max-width: 800px;
margin: 0 auto;
}
/* Card Styling */
.card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 1.5rem;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
/* Secondary Button Style */
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Form Field Styling */
.form-control:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
}
.form-select:focus {
border-color: var(--kaauh-teal);
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
}
/* Profile Image Upload Styling */
.profile-image-upload {
border: 2px dashed var(--kaauh-border);
border-radius: 0.5rem;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
}
.profile-image-upload:hover {
border-color: var(--kaauh-teal);
background-color: var(--kaauh-gray-light);
}
.profile-image-preview {
width: 120px;
height: 120px;
object-fit: cover;
border-radius: 50%;
border: 3px solid var(--kaauh-teal);
margin: 0 auto 1rem;
}
.current-image {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 50%;
border: 2px solid var(--kaauh-teal);
margin-right: 1rem;
}
/* Breadcrumb Styling */
.breadcrumb {
background-color: transparent;
padding: 0;
margin-bottom: 1rem;
}
.breadcrumb-item + .breadcrumb-item::before {
content: ">";
color: var(--kaauh-teal);
}
/* Alert Styling */
.alert {
border-radius: 0.5rem;
border: none;
}
/* Loading State */
.btn.loading {
position: relative;
pointer-events: none;
opacity: 0.8;
}
.btn.loading::after {
content: "";
position: absolute;
width: 16px;
height: 16px;
margin: auto;
border: 2px solid transparent;
border-top-color: #ffffff;
border-radius: 50%;
animation: spin 1s linear infinite;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Current Profile Section */
.current-profile {
background-color: var(--kaauh-gray-light);
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1rem;
}
.current-profile h6 {
color: var(--kaauh-teal-dark);
font-weight: 600;
margin-bottom: 0.75rem;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="px-4 py-6 max-w-4xl mx-auto">
<div class="form-container">
<!-- Breadcrumb Navigation --> <!-- Breadcrumb Navigation -->
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb" class="mb-6">
<ol class="breadcrumb"> <ol class="flex items-center space-x-2 text-sm">
<li class="breadcrumb-item"> <li>
<a href="{% url 'application_list' %}" class="text-decoration-none text-secondary"> <a href="{% url 'application_list' %}" class="text-gray-500 hover:text-temple-red transition">
<i class="fas fa-users me-1"></i> {% trans "Applications" %} <i data-lucide="users" class="w-4 h-4 inline mr-1"></i> {% trans "Applications" %}
</a> </a>
</li> </li>
<li class="breadcrumb-item"> <li class="text-gray-400">/</li>
<a href="{% url 'application_detail' object.slug %}" class="text-decoration-none text-secondary"> <li>
<a href="{% url 'application_detail' object.slug %}" class="text-gray-500 hover:text-temple-red transition">
{{ object.name }} {{ object.name }}
</a> </a>
</li> </li>
<li class="breadcrumb-item active" aria-current="page" <li class="text-temple-red font-semibold" aria-current="page">{% trans "Update" %}</li>
style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;">{% trans "Update" %}</li>
</ol> </ol>
</nav> </nav>
<!-- Header --> <!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6">
<h3 style="color: var(--kaauh-teal-dark);"> <h3 class="text-2xl font-bold text-temple-dark flex items-center gap-2">
<i class="fas fa-user-edit me-2"></i> {% trans "Update Application" %} <i data-lucide="user-edit" class="w-6 h-6"></i>
{% trans "Update Application" %}
</h3> </h3>
<div class="d-flex gap-2"> <div class="flex gap-2">
<a href="{% url 'application_detail' object.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'application_detail' object.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
<i class="fas fa-eye me-1"></i> {% trans "View Details" %} <i data-lucide="eye" class="w-4 h-4"></i>
{% trans "View Details" %}
</a> </a>
<a href="{% url 'application_delete' object.slug %}" class="btn btn-danger"> <a href="{% url 'application_delete' object.slug %}" class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
<i class="fas fa-trash me-1"></i> {% trans "Delete" %} <i data-lucide="trash-2" class="w-4 h-4"></i>
{% trans "Delete" %}
</a> </a>
<a href="{% url 'application_list' %}" class="btn btn-outline-secondary"> <a href="{% url 'application_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %} <i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Back to List" %}
</a> </a>
</div> </div>
</div> </div>
<!-- Current Profile Info --> <!-- Current Profile Info -->
<div class="card shadow-sm mb-4"> <div class="bg-white rounded-xl shadow-md border border-gray-200 mb-6">
<div class="card-body"> <div class="p-6">
<div class="current-profile"> <div class="bg-gray-50 rounded-lg p-4">
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6> <h6 class="text-sm font-semibold text-temple-dark mb-3 flex items-center gap-2">
<div class="d-flex align-items-center"> <i data-lucide="info" class="w-4 h-4"></i>
{% trans "Currently Editing" %}
</h6>
<div class="flex items-center gap-4">
{% if object.profile_image %} {% if object.profile_image %}
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}" <img src="{{ object.profile_image.url }}" alt="{{ object.name }}"
class="current-image"> class="w-24 h-24 rounded-full border-2 border-temple-red object-cover">
{% else %} {% else %}
<div class="current-image d-flex align-items-center justify-content-center bg-light"> <div class="w-24 h-24 rounded-full border-2 border-gray-300 bg-gray-100 flex items-center justify-center">
<i class="fas fa-user text-muted"></i> <i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
</div> </div>
{% endif %} {% endif %}
<div> <div>
<h5 class="mb-1">{{ object.name }}</h5> <h5 class="text-lg font-semibold text-gray-900 mb-1">{{ object.name }}</h5>
{% if object.email %} {% if object.email %}
<p class="text-muted mb-0">{{ object.email }}</p> <p class="text-gray-600 text-sm mb-1">{{ object.email }}</p>
{% endif %} {% endif %}
<small class="text-muted"> <p class="text-gray-500 text-xs">
{% trans "Created" %}: {{ object.created_at|date:"d M Y" }} • {% trans "Created" %}: {{ object.created_at|date:"d M Y" }} •
{% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }} {% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }}
</small> </p>
</div> </div>
</div> </div>
</div> </div>
@ -233,24 +78,24 @@
</div> </div>
<!-- Form Card --> <!-- Form Card -->
<div class="card shadow-sm"> <div class="bg-white rounded-xl shadow-md border border-gray-200">
<div class="card-body p-4"> <div class="p-6">
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="alert alert-danger" role="alert"> <div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4" role="alert">
<h5 class="alert-heading"> <h5 class="font-semibold text-red-800 flex items-center gap-2 mb-2">
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %} <i data-lucide="alert-triangle" class="w-5 h-5"></i>
{% trans "Error" %}
</h5> </h5>
{% for error in form.non_field_errors %} {% for error in form.non_field_errors %}
<p class="mb-0">{{ error }}</p> <p class="text-red-700 text-sm">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
{% if messages %} {% if messages %}
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert"> <div class="rounded-lg p-4 mb-4 {% if message.tags == 'success' %}bg-green-50 border border-green-200 text-green-800{% elif message.tags == 'error' %}bg-red-50 border border-red-200 text-red-800{% else %}bg-blue-50 border border-blue-200 text-blue-800{% endif %}" role="alert">
{{ message }} {{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
</div> </div>
{% endfor %} {% endfor %}
{% endif %} {% endif %}
@ -259,20 +104,41 @@
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
</form> </form>
<div class="d-flex gap-2">
<button form="candidate-form" type="submit" class="btn btn-main-action"> <div class="flex gap-2 mt-6">
<i class="fas fa-save me-1"></i> {% trans "Update" %} <button form="candidate-form" 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">
<i data-lucide="save" class="w-5 h-5"></i>
{% trans "Update" %}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}
{% block customJS %} {% block customJS %}
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
lucide.createIcons();
// Add form styling
const formInputs = document.querySelectorAll('input:not([type="hidden"]), select, textarea');
formInputs.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');
});
// Style form labels and containers
const formGroups = document.querySelectorAll('.form-group');
formGroups.forEach(group => {
group.classList.add('mb-6');
});
const formLabels = document.querySelectorAll('label');
formLabels.forEach(label => {
label.classList.add('block', 'text-sm', 'font-semibold', 'text-gray-700', 'mb-2');
});
// Profile Image Preview // Profile Image Preview
const profileImageInput = document.getElementById('id_profile_image'); const profileImageInput = document.getElementById('id_profile_image');
const imagePreviewContainer = document.getElementById('image-preview-container'); const imagePreviewContainer = document.getElementById('image-preview-container');
@ -286,9 +152,9 @@ document.addEventListener('DOMContentLoaded', function() {
reader.onload = function(e) { reader.onload = function(e) {
if (imagePreviewContainer) { if (imagePreviewContainer) {
imagePreviewContainer.innerHTML = ` imagePreviewContainer.innerHTML = `
<img src="${e.target.result}" alt="Profile Preview" class="profile-image-preview"> <img src="${e.target.result}" alt="Profile Preview" class="w-32 h-32 rounded-full border-3 border-temple-red object-cover mx-auto mb-3">
<h5 class="text-muted mt-3">${file.name}</h5> <h5 class="text-gray-600 text-sm font-medium">${file.name}</h5>
<p class="text-muted small">{% trans "New photo selected" %}</p> <p class="text-gray-500 text-xs">{% trans "New photo selected" %}</p>
`; `;
} }
}; };
@ -305,15 +171,17 @@ document.addEventListener('DOMContentLoaded', function() {
if (form) { if (form) {
form.addEventListener('submit', function(e) { form.addEventListener('submit', function(e) {
const submitBtn = form.querySelector('button[type="submit"]'); const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.classList.add('loading');
// Add loading state
submitBtn.disabled = true; submitBtn.disabled = true;
submitBtn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 mr-2 animate-spin"></i> {% trans "Saving..." %}`;
// Basic validation // Basic validation
const name = document.getElementById('id_name'); const name = document.getElementById('id_name');
if (name && !name.value.trim()) { if (name && !name.value.trim()) {
e.preventDefault(); e.preventDefault();
submitBtn.classList.remove('loading');
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Update" %}`;
alert('{% trans "Name is required." %}'); alert('{% trans "Name is required." %}');
return; return;
} }
@ -321,8 +189,8 @@ document.addEventListener('DOMContentLoaded', function() {
const email = document.getElementById('id_email'); const email = document.getElementById('id_email');
if (email && email.value.trim() && !isValidEmail(email.value.trim())) { if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
e.preventDefault(); e.preventDefault();
submitBtn.classList.remove('loading');
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Update" %}`;
alert('{% trans "Please enter a valid email address." %}'); alert('{% trans "Please enter a valid email address." %}');
return; return;
} }
@ -337,9 +205,9 @@ document.addEventListener('DOMContentLoaded', function() {
// Warn before leaving if changes are made // Warn before leaving if changes are made
let formChanged = false; let formChanged = false;
const formInputs = form ? form.querySelectorAll('input, select, textarea') : []; const formInputsTrack = form ? form.querySelectorAll('input, select, textarea') : [];
formInputs.forEach(input => { formInputsTrack.forEach(input => {
input.addEventListener('change', function() { input.addEventListener('change', function() {
formChanged = true; formChanged = true;
}); });

File diff suppressed because it is too large Load Diff

View File

@ -1,323 +1,182 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% blocktrans %}Exam Stage- {{ job.title }} - ATS {% endblocktrans %}{% endblock %} {% block title %}{% blocktrans %}Exam Stage - {{ job.title }} - University ATS{% endblocktrans %}{% endblock %}
{% block customCSS %}
<style>
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745;
--kaauh-info: #17a2b8;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
/* Primary Color Overrides */
.text-primary-theme { color: var(--kaauh-teal) !important; }
.bg-primary-theme { background-color: var(--kaauh-teal) !important; }
/* 1. Main Container & Card Styling */
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Dedicated style for the filter block */
.filter-controls {
background-color: #f8f9fa;
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 2rem;
border: 1px solid var(--kaauh-border);
}
/* 2. Button Styling (Themed for Main Actions) */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* Style for the Bulk Move button */
.btn-bulk-action {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
color: white;
font-weight: 500;
}
.btn-bulk-action:hover {
background-color: #00363e;
border-color: #00363e;
}
/* 3. Application Table Styling (Aligned with KAAT-S) */
.application-table {
table-layout: fixed;
width: 100%;
border-collapse: separate;
border-spacing: 0;
background-color: white;
border-radius: 0.5rem;
overflow: hidden;
}
.application-table thead {
background-color: var(--kaauh-border);
}
.application-table th {
padding: 0.75rem 1rem;
font-weight: 600;
color: var(--kaauh-teal-dark);
border-bottom: 2px solid var(--kaauh-teal);
font-size: 0.9rem;
vertical-align: middle;
}
.application-table td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle;
font-size: 0.9rem;
}
.application-table tbody tr:hover {
background-color: #f1f3f4;
}
.application-table thead th:nth-child(1) { width: 40px; }
.application-table thead th:nth-child(4) { width: 10%; }
.application-table thead th:nth-child(7) { width: 100px; }
.application-name {
font-weight: 600;
color: var(--kaauh-primary-text);
}
.application-details {
font-size: 0.8rem;
color: #6c757d;
}
/* 4. Badges and Statuses */
.ai-score-badge {
background-color: var(--kaauh-teal-dark) !important;
color: white;
font-weight: 700;
padding: 0.4em 0.8em;
border-radius: 0.4rem;
}
.status-badge {
font-size: 0.75rem;
padding: 0.3em 0.7em;
border-radius: 0.35rem;
font-weight: 700;
}
.bg-applicant { background-color: #6c757d !important; color: white; }
.bg-application { background-color: var(--kaauh-success) !important; color: white; }
/* Stage Badges */
.stage-badge {
font-size: 0.75rem;
padding: 0.25rem 0.6rem;
border-radius: 0.3rem;
font-weight: 600;
display: inline-block;
margin-bottom: 0.2rem;
}
.stage-Applied { background-color: #e9ecef; color: #495057; }
.stage-Screening { background-color: var(--kaauh-info); color: white; }
.stage-Exam { background-color: var(--kaauh-warning); color: #856404; }
.stage-Interview { background-color: #17a2b8; color: white; }
.stage-Offer { background-color: var(--kaauh-success); color: white; }
/* Timeline specific container */
.applicant-tracking-timeline {
margin-bottom: 2rem;
}
/* --- CUSTOM HEIGHT OPTIMIZATION (MAKING INPUTS/BUTTONS SMALLER) --- */
.form-control-sm,
.btn-sm {
/* Reduce vertical padding even more than default Bootstrap 'sm' */
padding-top: 0.2rem !important;
padding-bottom: 0.2rem !important;
/* Ensure a consistent, small height for both */
height: 28px !important;
font-size: 0.8rem !important; /* Slightly smaller font */
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="p-3 sm:p-4 lg:p-8">
<!-- Page Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4">
<div> <div>
<h1 class="h3 mb-1 page-header"> <h1 class="text-2xl sm:text-3xl font-bold text-temple-dark mb-2 flex items-center gap-2">
<i class="fas fa-edit me-2"></i> <i data-lucide="edit-3" class="w-6 h-6 sm:w-7 sm:h-7"></i>
{% trans "Exam Management" %} - {{ job.title }} {% trans "Exam Management" %}
</h1> </h1>
<h2 class="h5 text-muted mb-0"> <p class="text-gray-500 text-sm sm:text-base">
{% trans "Applications in Exam Stage:" %} <span class="fw-bold">{{ total_candidates }}</span> {% trans "Job:" %} {{ job.title }}
</h2> <span class="inline-block ml-2 px-2 py-0.5 bg-gray-200 text-gray-700 rounded-full text-xs font-normal">
{{ job.internal_job_id }}
</span>
</p>
</div> </div>
<div class="d-flex gap-2"> <div class="flex gap-2 w-full sm:w-auto">
<a href="{% url 'export_applications_csv' job.slug 'exam' %}" <a href="{% url 'export_applications_csv' job.slug 'exam' %}"
class="btn btn-outline-secondary" class="flex items-center gap-2 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"
title="{% trans 'Export exam applications to CSV' %}"> title="{% trans 'Export exam applications to CSV' %}">
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %} <i data-lucide="download" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Export CSV" %}</span>
</a> </a>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'job_detail' job.slug %}"
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Job" %} class="flex items-center gap-2 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">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Back to Job" %}</span>
</a> </a>
</div> </div>
</div> </div>
<div class="applicant-tracking-timeline mb-4"> <!-- Applicant Tracking Timeline -->
<div class="mb-6">
{% include 'jobs/partials/applicant_tracking.html' %} {% include 'jobs/partials/applicant_tracking.html' %}
</div> </div>
<h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);"> <!-- Application List Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4 gap-3">
<h2 class="text-xl font-bold text-temple-dark flex items-center gap-2">
<i data-lucide="users" class="w-5 h-5"></i>
{% trans "Application List" %} {% trans "Application List" %}
<span class="badge bg-primary-theme ms-2">{{ applications|length }} / {{ total_candidates }} Total</span> <span class="ml-2 px-2 py-1 bg-temple-red text-white text-xs rounded-full">
<small class="text-muted fw-normal ms-2">({% trans "Sorted by AI Score" %})</small> {{ applications|length }} / {{ total_candidates }} {% trans "Total" %}
</span>
</h2> </h2>
<div class="text-xs text-gray-500">
{% trans "Sorted by AI Score" %}
</div>
</div>
<div class="kaauh-card shadow-sm p-3"> <!-- Main Card -->
<div class="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
<!-- Bulk Action Bar -->
{% if applications %} {% if applications %}
<div class="bulk-action-bar p-3 bg-light border-bottom"> <div class="p-3 sm:p-4 bg-gray-50 border-b border-gray-200">
<form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group"> <form hx-boost="true" hx-include="#application-form"
action="{% url 'application_update_status' job.slug %}"
method="post"
class="flex flex-col sm:flex-row gap-3 sm:gap-4 items-end">
{% csrf_token %} {% csrf_token %}
{# Using d-flex for horizontal alignment and align-items-end to align items to the bottom baseline #} <div class="flex-1 w-full sm:w-auto">
<div class="d-flex align-items-end gap-3"> <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">
{# Select Input Group #} <option selected>----------</option>
<div> <option value="Interview">{% trans "Interview Stage" %}</option>
<option value="Applied">{% trans "Screening Stage" %}</option>
<select name="mark_as" id="update_status" class="form-select form-select-sm" style="min-width: 150px;">
<option selected>
----------
</option>
<option value="Interview">
{% trans "Interview Stage" %}
</option>
<option value="Applied">
{% trans "Screening Stage" %}
</option>
</select> </select>
</div> </div>
{# Button #} <button id="changeStage" type="submit"
<button id="changeStage" type="submit" class="btn btn-main-action btn-sm"> 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 class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %} <i data-lucide="arrow-right" class="w-4 h-4"></i>
{% trans "Change Stage" %}
</button> </button>
<button id="emailBotton" type="button" class="btn btn-outline-primary btn-sm" <button id="emailBotton" type="button"
data-bs-toggle="modal" 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"
hx-boost='true' onclick="openEmailModal()"
data-bs-target="#emailModal" title="{% trans 'Email Participants' %}">
hx-get="{% url 'compose_application_email' job.slug %}" <i data-lucide="mail" class="w-4 h-4"></i>
hx-target="#emailModalBody"
hx-include="#application-form"
title="Email Participants">
<i class="fas fa-envelope"></i>
</button> </button>
</div>
</form> </form>
</div> </div>
{% endif %} {% endif %}
<div class="table-responsive">
<form id="application-form" method="post"> <!-- Table -->
<div class="overflow-x-auto">
<form id="application-form" action="{% url 'application_update_status' job.slug %}" method="post">
{% csrf_token %} {% csrf_token %}
<table class="table application-table align-middle"> <table class="w-full border-collapse">
<thead> <thead class="bg-gray-50">
<tr> <tr>
<th style="width: 2%;"> <th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[2%]">
{% if applications %} {% if applications %}
<div class="form-check"> <div class="flex items-center">
<input <input type="checkbox" class="h-4 w-4 text-temple-red rounded border-gray-300 focus:ring-temple-red" id="selectAllCheckbox">
type="checkbox" class="form-check-input" id="selectAllCheckbox">
</div> </div>
{% endif %} {% endif %}
</th> </th>
<th style="width: 15%;">{% trans "Name" %}</th> <th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
<th style="width: 15%;">{% trans "Contact Info" %}</th> <i data-lucide="user" class="w-3 h-3 inline mr-1"></i> {% trans "Name" %}
<th style="width: 10%;" class="text-center">{% trans "AI Score" %}</th> </th>
<th style="width: 10%;">{% trans "Exam Date" %}</th> <th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
<th style="width: 10%;">{% trans "Exam Score" %}</th> <i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {% trans "Contact Info" %}
<th style="width: 10%;" class="text-center">{% trans "Exam Results" %}</th> </th>
<th style="width: 10%"> {% trans "Notes"%}</th> <th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
<th style="width: 15%;">{% trans "Actions" %}</th> <i data-lucide="bot" class="w-3 h-3 inline mr-1"></i> {% trans "AI Score" %}
</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
{% trans "Exam Date" %}
</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
{% trans "Exam Score" %}
</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
{% trans "Exam Results" %}
</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
{% trans "Notes" %}
</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[18%]">
<i data-lucide="settings" class="w-3 h-3 inline mr-1"></i> {% trans "Actions" %}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for application in applications %} {% for application in applications %}
<tr> <tr class="hover:bg-gray-50 transition-colors">
<td> <td class="px-4 py-3 border-b border-gray-200">
<div class="form-check"> <div class="flex items-center">
<input <input name="candidate_ids" value="{{ application.id }}"
name="candidate_ids" type="checkbox"
value="{{ application.id }}" class="h-4 w-4 text-temple-red rounded border-gray-300 focus:ring-temple-red rowCheckbox"
type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}"> id="application-{{ application.id }}">
</div> </div>
</td> </td>
<td> <td class="px-4 py-3 border-b border-gray-200">
<div class="application-name"> <div class="font-semibold text-temple-red">
{{ application.name }} {{ application.name }}
</div> </div>
</td> </td>
<td> <td class="px-4 py-3 border-b border-gray-200">
<div class="application-details"> <div class="text-xs text-gray-500">
<i class="fas fa-envelope me-1"></i> {{ application.email }}<br> <i data-lucide="mail" class="w-3 h-3 inline mr-1"></i> {{ application.email }}<br>
<i class="fas fa-phone me-1"></i> {{ application.phone }} <i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {{ application.phone }}
</div> </div>
</td> </td>
<td class="text-center"> <td class="px-4 py-3 text-center border-b border-gray-200">
<span class="badge ai-score-badge">{{ application.match_score|default:"0" }}%</span> <span class="inline-block px-2 py-1 bg-temple-red text-white text-xs font-bold rounded-md">
{{ application.match_score|default:"0" }}%
</span>
</td> </td>
<td> <td class="px-4 py-3 border-b border-gray-200 text-sm">
{{application.exam_date|date:"d-m-Y h:i A"|default:"--"}} {{application.exam_date|date:"d-m-Y h:i A"|default:"--"}}
</td> </td>
<td id="exam-score-{{ application.pk}}"> <td class="px-4 py-3 border-b border-gray-200 text-sm" id="exam-score-{{ application.pk }}">
{{application.exam_score|default:"--"}} {{application.exam_score|default:"--"}}
</td> </td>
<td class="px-4 py-3 text-center border-b border-gray-200" id="status-result-{{ application.pk }}">
<td class="text-center" id="status-result-{{ application.pk}}">
{% if not application.exam_status %} {% if not application.exam_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 flex items-center gap-1 mx-auto"
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 'Pass Exam' %}">
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.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 %}
@ -325,34 +184,31 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
<td><button type="button" class="btn btn-outline-primary btn-sm" <td class="px-4 py-3 border-b border-gray-200">
data-bs-toggle="modal" <button type="button"
data-bs-target="#noteModal" 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 flex items-center gap-1 mx-auto"
hx-get="{% url 'application_add_note' application.slug %}" onclick="openNoteModal('{% url 'application_add_note' application.slug %}')">
hx-swap="innerHTML" <i data-lucide="plus-circle" class="w-3 h-3"></i>
hx-target=".notemodal"> {% trans "Add note" %}
<i class="fas fa-calendar-plus me-1"></i> </button>
Add note </td>
</button></td> <td class="px-4 py-3 border-b border-gray-200 text-center">
<button type="button"
<td > class="px-3 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"
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="openCandidateModal('{% url 'application_criteria_view_htmx' application.pk %}')"
data-bs-toggle="modal" title="{% trans 'View Application Profile' %}">
data-bs-target="#candidateviewModal" <i data-lucide="eye" class="w-3 h-3"></i>
hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
hx-target="#candidateviewModalBody"
title="View Profile">
<i class="fas fa-eye"></i>
</button> </button>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% if not applications %} {% if not applications %}
<div class="alert alert-info text-center mt-3 mb-0" role="alert"> <div class="p-8 text-center bg-blue-50 border border-blue-200 rounded-lg m-4">
<i class="fas fa-info-circle me-1"></i> <i data-lucide="info" class="w-8 h-8 text-blue-500 mx-auto mb-2"></i>
{% trans "No applications are currently in the Exam stage for this job." %} <p class="text-blue-700 text-sm">{% trans "No applications are currently in the Exam stage for this job." %}</p>
</div> </div>
{% endif %} {% endif %}
</form> </form>
@ -360,23 +216,30 @@
</div> </div>
</div> </div>
<div class="modal fade modal-lg" id="candidateviewModal" tabindex="-1" aria-labelledby="candidateviewModalLabel" aria-hidden="true"> <!-- Candidate View Modal -->
<div class="modal-dialog"> <div id="candidateviewModal" class="fixed inset-0 z-50 hidden" aria-labelledby="candidateviewModalLabel" role="dialog" aria-modal="true">
<div class="modal-content kaauh-card"> <!-- Backdrop -->
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeCandidateModal()"></div>
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
<!-- Modal Content -->
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<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="candidateviewModalLabel">
{% trans "Application Details & Exam Update" %} {% trans "Application Details & Exam Update" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeCandidateModal()" aria-label="Close">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div> </div>
<div id="candidateviewModalBody" class="modal-body"> <div id="candidateviewModalBody" class="flex-1 overflow-y-auto p-6">
<div class="text-center py-5 text-muted"> <div class="text-center py-10 text-gray-500">
<i class="fas fa-spinner fa-spin fa-2x"></i><br> <i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
{% trans "Loading application data..." %} <p>{% trans "Loading application data..." %}</p>
</div> </div>
</div> </div>
<div class="modal-footer" style="border-top: 1px solid var(--kaauh-border);"> <div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"> <button type="button" class="w-full px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeCandidateModal()">
{% trans "Close" %} {% trans "Close" %}
</button> </button>
</div> </div>
@ -384,47 +247,260 @@
</div> </div>
</div> </div>
<!-- Email Modal --> <!-- Email Modal -->
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true"> <div id="emailModal" class="fixed inset-0 z-50 hidden" aria-labelledby="emailModalLabel" role="dialog" aria-modal="true">
<div class="modal-dialog modal-lg" role="document"> <!-- Backdrop -->
<div class="modal-content kaauh-card"> <div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeEmailModal()"></div>
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="emailModalLabel" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-envelope me-2"></i>{% trans "Compose Email" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div id="emailModalBody" class="modal-body">
<div class="text-center py-5 text-muted">
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
{% trans "Loading email form..." %}
<!-- Modal Content -->
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<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="emailModalLabel">
<i data-lucide="mail" class="w-5 h-5 inline mr-2"></i>{% trans "Compose Email" %}
</h5>
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeEmailModal()" aria-label="Close">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div id="emailModalBody" class="flex-1 overflow-y-auto p-6">
<div class="text-center py-10 text-gray-500">
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
<p>{% trans "Loading email form..." %}</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% include "recruitment/partials/note_modal.html" %}
{% include "recruitment/partials/stage_confirmation_modal.html" %} <!-- Note Modal -->
<div id="noteModal" class="fixed inset-0 z-50 hidden" aria-labelledby="noteModalLabel" role="dialog" aria-modal="true">
<!-- Backdrop -->
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeNoteModal()"></div>
<!-- Modal Content -->
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<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" %}
</h5>
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeNoteModal()" aria-label="Close">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div id="noteModalBody" class="flex-1 overflow-y-auto p-6">
<div class="text-center py-10 text-gray-500">
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
<p>{% trans "Loading note form..." %}</p>
</div>
</div>
</div>
</div>
</div>
<!-- Stage Confirmation Modal -->
<div id="stageConfirmationModal" class="fixed inset-0 z-50 hidden" aria-labelledby="stageConfirmationModalLabel" role="dialog" aria-modal="true">
<!-- Backdrop -->
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeStageConfirmationModal()"></div>
<!-- Modal Content -->
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-lg">
<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="stageConfirmationModalLabel">
<i data-lucide="info" class="w-5 h-5 inline mr-2"></i>{% trans "Confirm Stage Change" %}
</h5>
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeStageConfirmationModal()" aria-label="Close">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div class="p-6">
<div class="flex items-center justify-center py-3 mb-3">
<i data-lucide="arrow-right-left" class="w-16 h-16 text-temple-red"></i>
</div>
<p class="text-center mb-2 text-base text-gray-800">
<span id="stageConfirmationMessage">{% trans "Are you sure you want to change to this stage?" %}</span>
</p>
<div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg text-center" role="alert">
<i data-lucide="user-check" class="w-4 h-4 inline mr-2"></i>
<span class="font-semibold">{% trans "Selected Stage:" %}</span>
<span id="targetStageName" class="font-bold">{% trans "--" %}</span>
</div>
</div>
<div class="px-6 py-4 border-t border-gray-200 flex justify-end gap-2">
<button type="button" class="px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeStageConfirmationModal()">
<i data-lucide="x" class="w-4 h-4 inline mr-1"></i>{% trans "Cancel" %}
</button>
<button type="button" class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium" id="confirmStageChangeButton">
<i data-lucide="check" class="w-4 h-4 inline mr-1"></i>{% trans "Confirm" %}
</button>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block customJS %} {% block customJS %}
<script> <script>
// Reinitialize Lucide icons after content loads
document.addEventListener('DOMContentLoaded', function () {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
// ========================================
// Modal Functions
// ========================================
function openCandidateModal(url) {
const modal = document.getElementById('candidateviewModal');
const modalBody = document.getElementById('candidateviewModalBody');
// Reset content
modalBody.innerHTML = `
<div class="text-center py-10 text-gray-500">
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
<p>{% trans "Loading application data..." %}</p>
</div>
`;
// Show modal
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// Load content via HTMX
if (url && typeof htmx !== 'undefined') {
htmx.ajax('GET', url, {target: '#candidateviewModalBody', swap: 'innerHTML'});
}
// Reinitialize icons
setTimeout(() => {
if (typeof lucide !== 'undefined') lucide.createIcons();
}, 100);
}
function closeCandidateModal() {
const modal = document.getElementById('candidateviewModal');
modal.classList.add('hidden');
document.body.style.overflow = '';
}
function openEmailModal() {
const modal = document.getElementById('emailModal');
const modalBody = document.getElementById('emailModalBody');
const applicationForm = document.getElementById('application-form');
const url = '{% url "compose_application_email" job.slug %}';
// Reset content
modalBody.innerHTML = `
<div class="text-center py-10 text-gray-500">
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
<p>{% trans "Loading email form..." %}</p>
</div>
`;
// Show modal
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// Load content via HTMX with form data
if (url && typeof htmx !== 'undefined' && applicationForm) {
htmx.ajax('GET', url, {
target: '#emailModalBody',
swap: 'innerHTML',
values: htmx.values(applicationForm)
});
}
// Reinitialize icons
setTimeout(() => {
if (typeof lucide !== 'undefined') lucide.createIcons();
}, 100);
}
function closeEmailModal() {
const modal = document.getElementById('emailModal');
modal.classList.add('hidden');
document.body.style.overflow = '';
}
function openNoteModal(url) {
const modal = document.getElementById('noteModal');
const modalBody = document.getElementById('noteModalBody');
// Reset content
modalBody.innerHTML = `
<div class="text-center py-10 text-gray-500">
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
<p>{% trans "Loading note form..." %}</p>
</div>
`;
// Show modal
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// Load content via HTMX
if (url && typeof htmx !== 'undefined') {
htmx.ajax('GET', url, {target: '#noteModalBody', swap: 'innerHTML'});
}
// Reinitialize icons
setTimeout(() => {
if (typeof lucide !== 'undefined') lucide.createIcons();
}, 100);
}
function closeNoteModal() {
const modal = document.getElementById('noteModal');
modal.classList.add('hidden');
document.body.style.overflow = '';
}
function openStageConfirmationModal(selectedStage) {
const modal = document.getElementById('stageConfirmationModal');
const messageElement = document.getElementById('stageConfirmationMessage');
const targetStageElement = document.getElementById('targetStageName');
// Update confirmation message
if (messageElement && targetStageElement) {
const checkedCount = Array.from(document.querySelectorAll('.rowCheckbox:checked')).length;
if (checkedCount > 0) {
messageElement.textContent = `{% trans "Are you sure you want to move" %} ${checkedCount} {% trans "candidate(s) to this stage?" %}`;
targetStageElement.textContent = selectedStage;
} else {
messageElement.textContent = '{% trans "Please select at least one candidate." %}';
targetStageElement.textContent = '--';
}
}
// Show modal
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
}
function closeStageConfirmationModal() {
const modal = document.getElementById('stageConfirmationModal');
modal.classList.add('hidden');
document.body.style.overflow = '';
}
// ========================================
// Checkbox and Form Logic
// ========================================
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const selectAllCheckbox = document.getElementById('selectAllCheckbox'); const selectAllCheckbox = document.getElementById('selectAllCheckbox');
const rowCheckboxes = document.querySelectorAll('.rowCheckbox'); const rowCheckboxes = document.querySelectorAll('.rowCheckbox');
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 stageConfirmationModal = new bootstrap.Modal(document.getElementById('stageConfirmationModal'));
const confirmStageChangeButton = document.getElementById('confirmStageChangeButton'); const confirmStageChangeButton = document.getElementById('confirmStageChangeButton');
let isConfirmed = false; let isConfirmed = false;
if (selectAllCheckbox) { if (selectAllCheckbox) {
// Function to safely update the 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;
@ -433,26 +509,25 @@
if (checkedCount === 0) { if (checkedCount === 0) {
selectAllCheckbox.checked = false; selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false; selectAllCheckbox.indeterminate = false;
changeStageButton.disabled = true; if (changeStageButton) changeStageButton.disabled = true;
emailButton.disabled = true; if (emailButton) emailButton.disabled = true;
updateStatus.disabled = true; if (updateStatus) updateStatus.disabled = true;
} else if (checkedCount === totalCount) { } else if (checkedCount === totalCount) {
selectAllCheckbox.checked = true; selectAllCheckbox.checked = true;
selectAllCheckbox.indeterminate = false; selectAllCheckbox.indeterminate = false;
changeStageButton.disabled = false; if (changeStageButton) changeStageButton.disabled = false;
emailButton.disabled = false; if (emailButton) emailButton.disabled = false;
updateStatus.disabled = false; if (updateStatus) updateStatus.disabled = false;
} else { } else {
// Set to indeterminate state (partially checked)
selectAllCheckbox.checked = false; selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = true; selectAllCheckbox.indeterminate = true;
changeStageButton.disabled = false; if (changeStageButton) changeStageButton.disabled = false;
emailButton.disabled = false; if (emailButton) emailButton.disabled = false;
updateStatus.disabled = false; if (updateStatus) updateStatus.disabled = false;
} }
} }
// 1. Logic for the 'Select All' checkbox (Clicking it updates all rows) // Logic for 'Select All' checkbox
selectAllCheckbox.addEventListener('change', function () { selectAllCheckbox.addEventListener('change', function () {
const isChecked = selectAllCheckbox.checked; const isChecked = selectAllCheckbox.checked;
@ -467,12 +542,10 @@
updateSelectAllState(); updateSelectAllState();
}); });
// 2. Logic to update 'Select All' state based on row checkboxes
rowCheckboxes.forEach(function (checkbox) { rowCheckboxes.forEach(function (checkbox) {
checkbox.addEventListener('change', updateSelectAllState); checkbox.addEventListener('change', updateSelectAllState);
}); });
// Initial check to set the correct state on load (in case items are pre-checked)
updateSelectAllState(); updateSelectAllState();
} }
@ -481,51 +554,34 @@
changeStageButton.addEventListener('click', function(event) { changeStageButton.addEventListener('click', function(event) {
const selectedStage = updateStatus.value; const selectedStage = updateStatus.value;
// Check if a stage is selected (not default empty option)
if (selectedStage && selectedStage.trim() !== '') { if (selectedStage && selectedStage.trim() !== '') {
// If not yet confirmed, show modal and prevent submission
if (!isConfirmed) { if (!isConfirmed) {
event.preventDefault(); event.preventDefault();
openStageConfirmationModal(selectedStage);
// Count selected candidates
const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
// Update confirmation message
const messageElement = document.getElementById('stageConfirmationMessage');
const targetStageElement = document.getElementById('targetStageName');
if (messageElement && targetStageElement) {
if (checkedCount > 0) {
messageElement.textContent = `{% trans "Are you sure you want to move" %} ${checkedCount} {% trans "candidate(s) to this stage?" %}`;
targetStageElement.textContent = selectedStage;
} else {
messageElement.textContent = '{% trans "Please select at least one candidate." %}';
targetStageElement.textContent = '--';
}
}
// Show confirmation modal
stageConfirmationModal.show();
return false; return false;
} }
// If confirmed, let's form submit normally (reset flag for next time)
isConfirmed = false; isConfirmed = false;
} }
}); });
// Handle confirm button click in modal
if (confirmStageChangeButton) { if (confirmStageChangeButton) {
confirmStageChangeButton.addEventListener('click', function() { confirmStageChangeButton.addEventListener('click', function() {
// Hide modal closeStageConfirmationModal();
stageConfirmationModal.hide();
// Set confirmed flag
isConfirmed = true; isConfirmed = true;
// Programmatically trigger's button click to submit form
changeStageButton.click(); changeStageButton.click();
}); });
} }
} }
// Close modals on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeCandidateModal();
closeEmailModal();
closeNoteModal();
closeStageConfirmationModal();
}
});
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,438 +1,259 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% trans "Hired Stage" %} {{ job.title }} - ATS{% endblock %} {% block title %}{% blocktrans %}Hired Stage - {{ job.title }} - University ATS{% endblocktrans %}{% endblock %}
{% block customCSS %}
<style>
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-success: #28a745;
--kaauh-info: #17a2b8;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
/* Primary Color Overrides */
.text-primary-theme { color: var(--kaauh-teal) !important; }
.bg-primary-theme { background-color: var(--kaauh-teal) !important; }
/* 1. Main Container & Card Styling */
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Dedicated style for the filter block */
.filter-controls {
background-color: #f8f9fa;
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 2rem;
border: 1px solid var(--kaauh-border);
}
/* 2. Button Styling (Themed for Main Actions) */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-main-action:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.btn-outline-secondary {
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
}
/* 3. Application Table Styling (Aligned with KAAT-S) */
.application-table {
table-layout: fixed;
width: 100%;
border-collapse: separate;
border-spacing: 0;
background-color: white;
border-radius: 0.5rem;
overflow: hidden;
}
.application-table thead {
background-color: var(--kaauh-border);
}
.application-table th {
padding: 0.75rem 1rem;
font-weight: 600;
color: var(--kaauh-teal-dark);
border-bottom: 2px solid var(--kaauh-teal);
font-size: 0.9rem;
vertical-align: middle;
}
.application-table td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--kaauh-border);
vertical-align: middle;
font-size: 0.9rem;
}
.application-table tbody tr:hover {
background-color: #f1f3f4;
}
.application-table thead th:nth-child(1) { width: 40px; }
.application-table thead th:nth-child(4) { width: 10%; }
.application-table thead th:nth-child(7) { width: 100px; }
.application-name {
font-weight: 600;
color: var(--kaauh-primary-text);
}
.application-details {
font-size: 0.8rem;
color: #6c757d;
}
/* 4. Badges and Statuses */
.ai-score-badge {
background-color: var(--kaauh-teal-dark) !important;
color: white;
font-weight: 700;
padding: 0.4em 0.8em;
border-radius: 0.4rem;
}
.status-badge {
font-size: 0.75rem;
padding: 0.3em 0.7em;
border-radius: 0.35rem;
font-weight: 700;
}
.bg-applicant { background-color: #6c757d !important; color: white; }
.bg-application { background-color: var(--kaauh-success) !important; color: white; }
/* Stage Badges */
.stage-badge {
font-size: 0.75rem;
padding: 0.25rem 0.6rem;
border-radius: 0.3rem;
font-weight: 600;
display: inline-block;
margin-bottom: 0.2rem;
}
.stage-Applied { background-color: #e9ecef; color: #495057; }
.stage-Screening { background-color: var(--kaauh-info); color: white; }
.stage-Exam { background-color: var(--kaauh-warning); color: #856404; }
.stage-Interview { background-color: #17a2b8; color: white; }
.stage-Offer { background-color: var(--kaauh-success); color: white; }
.stage-Hired { background-color: #28a745; color: white; }
/* Timeline specific container */
.applicant-tracking-timeline {
margin-bottom: 2rem;
}
/* Hired-specific styling */
.hired-badge {
background-color: #28a745 !important;
color: white;
font-weight: 700;
padding: 0.5em 1em;
border-radius: 0.5rem;
font-size: 0.9rem;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.hired-date {
font-size: 0.8rem;
color: #6c757d;
margin-top: 0.25rem;
}
/* Success state styling */
.success-header {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
color: white;
padding: 1.5rem;
border-radius: 0.75rem;
margin-bottom: 2rem;
text-align: center;
}
/* --- CUSTOM HEIGHT OPTIMIZATION (MAKING INPUTS/BUTTONS SMALLER) --- */
.form-control-sm,
.btn-sm {
/* Reduce vertical padding even more than default Bootstrap 'sm' */
padding-top: 0.2rem !important;
padding-bottom: 0.2rem !important;
/* Ensure a consistent, small height for both */
height: 28px !important;
font-size: 0.8rem !important; /* Slightly smaller font */
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="p-3 sm:p-4 lg:p-8">
<!-- Page Header -->
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4">
<div> <div>
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="text-2xl sm:text-3xl font-bold text-temple-dark mb-2 flex items-center gap-2">
<i class="fas fa-trophy me-2"></i> <i data-lucide="trophy" class="w-6 h-6 sm:w-7 sm:h-7"></i>
{% trans "Hired Applications" %} - {{ job.title }} {% trans "Hired Applications" %} - {{ job.title }}
</h1> </h1>
<h2 class="h5 text-muted mb-0"> <h2 class="text-sm sm:text-base text-gray-500 mb-0">
{% trans "Successfully Hired:" %} <span class="fw-bold">{{ applications|length }}</span> {% trans "Successfully Hired:" %} <span class="font-bold">{{ applications|length }}</span>
</h2> </h2>
</div> </div>
<div class="d-flex gap-2"> <div class="flex gap-2 w-full sm:w-auto">
<a href="{% url 'export_applications_csv' job.slug 'hired' %}" <a href="{% url 'export_applications_csv' job.slug 'hired' %}"
class="btn btn-outline-secondary" class="flex items-center gap-2 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"
title="{% trans 'Export hired applications to CSV' %}"> title="{% trans 'Export hired applications to CSV' %}">
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %} <i data-lucide="download" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Export CSV" %}</span>
</a> </a>
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'job_detail' job.slug %}"
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Job" %} class="flex items-center gap-2 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">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Back to Job" %}</span>
</a> </a>
</div> </div>
</div> </div>
<!-- Success Header --> <!-- Success Header -->
<div class="success-header"> <div class="bg-gradient-to-br from-green-500 to-teal-500 text-white p-6 rounded-xl mb-6 text-center shadow-lg">
<i class="fas fa-check-circle fa-3x mb-3"></i> <i data-lucide="check-circle" class="w-12 h-12 mb-3 mx-auto"></i>
<h3 class="mb-2">{% trans "Congratulations!" %}</h3> <h3 class="text-xl font-bold mb-2">{% trans "Congratulations!" %}</h3>
<p class="mb-0">{% trans "These applications have successfully completed the hiring process and joined your team." %}</p> <p class="mb-0 opacity-90">{% trans "These applications have successfully completed the hiring process and joined your team." %}</p>
</div> </div>
<!-- ERP Sync Status --> <!-- ERP Sync Status -->
{% if job.source %} {% if job.source %}
<div class="kaauh-card shadow-sm p-3 mb-4"> <div class="bg-white border border-gray-200 rounded-xl shadow-sm p-4 sm:p-6 mb-6">
<div class="d-flex justify-content-between align-items-start"> <div class="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4">
<div> <div>
<h5 class="mb-2" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h5 class="text-lg font-bold text-temple-dark mb-4 flex items-center gap-2">
<i class="fas fa-database me-2"></i> {% trans "ERP Sync Status" %} <i data-lucide="database" class="w-5 h-5"></i> {% trans "ERP Sync Status" %}
</h5> </h5>
<div class="row g-3"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
<div class="col-md-3"> <div>
<small class="text-muted d-block">{% trans "Source:" %}</small> <small class="text-gray-500 block text-xs">{% trans "Source:" %}</small>
<strong>{{ job.source.name }}</strong> <strong class="text-sm">{{ job.source.name }}</strong>
</div> </div>
<div class="col-md-3"> <div>
<small class="text-muted d-block">{% trans "Sync Status:" %}</small> <small class="text-gray-500 block text-xs">{% trans "Sync Status:" %}</small>
<span class="badge <span class="px-2 py-1 rounded-full text-xs font-bold
{% if job.source.sync_status == 'SUCCESS' %}bg-success {% if job.source.sync_status == 'SUCCESS' %}bg-green-500 text-white
{% elif job.source.sync_status == 'SYNCING' %}bg-warning {% elif job.source.sync_status == 'SYNCING' %}bg-yellow-400 text-yellow-900
{% elif job.source.sync_status == 'ERROR' %}bg-danger {% elif job.source.sync_status == 'ERROR' %}bg-red-500 text-white
{% else %}bg-secondary{% endif %}"> {% else %}bg-gray-400 text-white{% endif %}">
{{ job.source.get_sync_status_display }} {{ job.source.get_sync_status_display }}
</span> </span>
</div> </div>
<div class="col-md-3"> <div>
<small class="text-muted d-block">{% trans "Last Sync:" %}</small> <small class="text-gray-500 block text-xs">{% trans "Last Sync:" %}</small>
<strong> <strong class="text-sm">
{% if job.source.last_sync_at %} {% if job.source.last_sync_at %}
{{ job.source.last_sync_at|date:"M d, Y H:i" }} {{ job.source.last_sync_at|date:"M d, Y H:i" }}
{% else %} {% else %}
<span class="text-muted">{% trans "Never" %}</span> <span class="text-gray-400">{% trans "Never" %}</span>
{% endif %} {% endif %}
</strong> </strong>
</div> </div>
<div class="col-md-3"> <div>
<small class="text-muted d-block">{% trans "Hired Candidates:" %}</small> <small class="text-gray-500 block text-xs">{% trans "Hired Candidates:" %}</small>
<strong>{{ applications|length }}</strong> <strong class="text-sm">{{ applications|length }}</strong>
</div> </div>
</div> </div>
</div> </div>
{# Manual sync button commented out - sync is now automatic via Django signals #}
{# <button type="button"
class="btn btn-main-action"
onclick="syncHiredCandidates()"
title="{% trans 'Manually sync hired applications to ERP source (use for re-syncs)' %}">
<i class="fas fa-sync me-1"></i> {% trans "Sync to Sources" %}
</button> #}
</div> </div>
<div class="alert alert-info mt-3 mb-0" style="font-size: 0.85rem;"> <div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg mt-4 text-sm" role="alert">
<i class="fas fa-info-circle me-2"></i> <i data-lucide="info" class="w-4 h-4 inline mr-2"></i>
{% trans "ERP sync is automatically triggered when candidates are moved to 'Hired' stage. Use the 'Sync to Sources' button for manual re-syncs if needed." %} {% trans "ERP sync is automatically triggered when candidates are moved to 'Hired' stage." %}
</div> </div>
</div> </div>
{% else %} {% else %}
<div class="alert alert-warning mb-4"> <div class="bg-yellow-50 border border-yellow-200 text-yellow-800 px-4 py-3 rounded-lg mb-6 text-sm" role="alert">
<i class="fas fa-exclamation-triangle me-2"></i> <i data-lucide="alert-triangle" class="w-4 h-4 inline mr-2"></i>
{% trans "No ERP source configured for this job. Automatic sync is disabled." %} {% trans "No ERP source configured for this job. Automatic sync is disabled." %}
</div> </div>
{% endif %} {% endif %}
<div class="applicant-tracking-timeline"> <!-- Applicant Tracking Timeline -->
<div class="mb-6">
{% include 'jobs/partials/applicant_tracking.html' %} {% include 'jobs/partials/applicant_tracking.html' %}
</div> </div>
<div class="kaauh-card shadow-sm p-3"> <!-- Application List Header -->
{% comment %} {% if applications %} <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4 gap-3">
<div class="bulk-action-bar p-3 bg-light border-bottom"> <h2 class="text-xl font-bold text-temple-dark flex items-center gap-2">
<form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group"> <i data-lucide="users" class="w-5 h-5"></i>
{% csrf_token %} {% trans "Hired Applications" %}
<span class="ml-2 px-2 py-1 bg-temple-red text-white text-xs rounded-full">
{# MODIFIED: Using d-flex for horizontal alignment and align-items-end to align everything based on the baseline of the button/select #} {{ applications|length }}
<div class="d-flex align-items-end gap-3"> </span>
</h2>
{# Select Input Group #}
<div>
<select name="mark_as" id="update_status" class="form-select form-select-sm" style="min-width: 150px;">
<option selected>
----------
</option>
<option value="Offer">
{% trans "Offer Stage" %}
</option>
{# Include other options here, such as Interview, Offer, Rejected, etc. #}
</select>
</div> </div>
{# Button #} <!-- Main Card -->
<button type="submit" class="btn btn-main-action btn-sm"> <div class="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %} <!-- Table -->
</button> <div class="overflow-x-auto">
{# email button#}
<button type="button" class="btn btn-outline-primary btn-sm"
data-bs-toggle="modal"
hx-boost='true'
data-bs-target="#emailModal"
hx-get="{% url 'compose_application_email' job.slug %}"
hx-target="#emailModalBody"
hx-include="#application-form"
title="Email Participants">
<i class="fas fa-envelope"></i>
</button>
</div>
</form>
</div>
{% endif %} {% endcomment %}
<div class="table-responsive">
<form id="application-form" action="{% url 'application_update_status' job.slug %}" method="get"> <form id="application-form" action="{% url 'application_update_status' job.slug %}" method="get">
{% csrf_token %} {% csrf_token %}
<table class="table application-table align-middle"> <table class="w-full border-collapse">
<thead> <thead class="bg-gray-50">
<tr> <tr>
{% comment %} <th style="width: 2%"> <th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
{% if applications %} <i data-lucide="user" class="w-3 h-3 inline mr-1"></i> {% trans "Name" %}
<div class="form-check"> </th>
<input <th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
type="checkbox" class="form-check-input" id="selectAllCheckbox"> <i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {% trans "Contact Info" %}
</div> </th>
{% endif %} <th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
</th> {% endcomment %} <i data-lucide="briefcase" class="w-3 h-3 inline mr-1"></i> {% trans "Applied Position" %}
<th style="width: 15%"><i class="fas fa-user me-1"></i> {% trans "Name" %}</th> </th>
<th style="width: 15%"><i class="fas fa-phone me-1"></i> {% trans "Contact" %}</th> <th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
<th style="width: 15%"><i class="fas fa-briefcase me-1"></i> {% trans "Applied Position" %}</th> <i data-lucide="calendar-check" class="w-3 h-3 inline mr-1"></i> {% trans "Hired Date" %}
<th class="text-center" style="width: 15%"><i class="fas fa-calendar-check me-1"></i> {% trans "Hired Date" %}</th> </th>
<th class="text-center" style="width: 15%"><i class="fas fa-calendar-check me-1"></i> {% trans "Status" %}</th> <th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
<th style="width: 15%"><i class="fas fa-cog me-1"></i> {% trans "Actions" %}</th> <i data-lucide="check-circle" class="w-3 h-3 inline mr-1"></i> {% trans "Status" %}
</th>
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
<i data-lucide="settings" class="w-3 h-3 inline mr-1"></i> {% trans "Actions" %}
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for application in applications %} {% for application in applications %}
<tr> <tr class="hover:bg-gray-50 transition-colors">
{% comment %} <td> <td class="px-4 py-3 border-b border-gray-200">
<div class="form-check"> <div class="font-semibold text-temple-dark">
<input name="candidate_ids" value="{{ application.id }}" type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
</div>
</td> {% endcomment %}
<td>
<div class="application-name">
{{ application.name }} {{ application.name }}
</div> </div>
</td> </td>
<td> <td class="px-4 py-3 border-b border-gray-200">
<div class="application-details"> <div class="text-xs text-gray-500">
<i class="fas fa-envelope me-1"></i> {{ application.email }}<br> <i data-lucide="mail" class="w-3 h-3 inline mr-1"></i> {{ application.email }}<br>
<i class="fas fa-phone me-1"></i> {{ application.phone }} <i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {{ application.phone }}
</div> </div>
</td> </td>
<td> <td class="px-4 py-3 border-b border-gray-200">
<div class="application-details"> <div class="text-xs text-gray-500">
<strong>{{ job.title }}</strong><br> <strong>{{ job.title }}</strong><br>
<small class="text-muted">{{ job.department }}</small> <small>{{ job.department }}</small>
</div> </div>
</td> </td>
<td class="text-center"> <td class="px-4 py-3 text-center border-b border-gray-200">
<div class="hired-date"> <div class="text-xs text-gray-500">
{% if application.offer_date %} {% if application.offer_date %}
<i class="fas fa-calendar me-1"></i> <i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
{{ application.offer_date|date:"M d, Y" }} {{ application.offer_date|date:"M d, Y" }}
{% else %} {% else %}
<span class="text-muted">--</span> <span class="text-gray-400">--</span>
{% endif %} {% endif %}
</div> </div>
</td> </td>
<td class="text-center"> <td class="px-4 py-3 text-center border-b border-gray-200">
<div class="hired-badge mt-1"> <div class="px-3 py-1.5 bg-green-500 text-white rounded-lg text-xs font-bold inline-flex items-center gap-1">
<i class="fas fa-check-circle"></i> <i data-lucide="check-circle" class="w-3 h-3"></i>
{% trans "Hired" %} {% trans "Hired" %}
</div> </div>
</td> </td>
<td> <td class="px-4 py-3 border-b border-gray-200 text-center">
<div class="btn-group" role="group"> <div class="flex items-center justify-center gap-1">
<button type="button" class="btn btn-outline-secondary btn-sm" <button type="button"
data-bs-toggle="modal" class="px-3 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"
data-bs-target="#candidateviewModal" onclick="openCandidateModal('{% url 'application_criteria_view_htmx' application.pk %}')"
hx-get="{% url 'application_criteria_view_htmx' application.pk %}" title="{% trans 'View Profile' %}">
hx-target="#candidateviewModalBody" <i data-lucide="eye" class="w-3 h-3"></i>
title="View Profile">
<i class="fas fa-eye"></i>
</button> </button>
<a href="{% url 'application_resume_template' application.slug %}" <a href="{% url 'application_resume_template' application.slug %}"
class="btn btn-outline-primary btn-sm" 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"
title="View Resume Template"> title="{% trans 'View Resume Template' %}">
<i class="fas fa-file-alt"></i> <i data-lucide="file-alt" class="w-3 h-3"></i>
</a> </a>
</div> </div>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</form>
{% if not applications %} {% if not applications %}
<div class="alert alert-info text-center mt-3 mb-0" role="alert"> <div class="p-8 text-center bg-blue-50 border border-blue-200 rounded-lg m-4">
<i class="fas fa-info-circle me-1"></i> <i data-lucide="info" class="w-8 h-8 text-blue-500 mx-auto mb-2"></i>
{% trans "No applications have been hired for this position yet." %} <p class="text-blue-700 text-sm">{% trans "No applications have been hired for this position yet." %}</p>
</div> </div>
{% endif %} {% endif %}
</form>
</div>
</div> </div>
</div> </div>
</div> <!-- Candidate View Modal -->
<div id="candidateviewModal" class="fixed inset-0 z-50 hidden" aria-labelledby="candidateviewModalLabel" role="dialog" aria-modal="true">
<!-- Backdrop -->
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeCandidateModal()"></div>
<div class="modal fade modal-xl" id="candidateviewModal" tabindex="-1" aria-labelledby="candidateviewModalLabel" aria-hidden="true"> <!-- Modal Content -->
<div class="modal-dialog"> <div class="relative z-10 flex min-h-screen items-center justify-center p-4">
<div class="modal-content kaauh-card"> <div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);"> <h5 class="text-lg font-bold text-temple-dark" id="candidateviewModalLabel">
{% trans "Hired Application Details" %} {% trans "Hired Application Details" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeCandidateModal()" aria-label="Close">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div> </div>
<div id="candidateviewModalBody" class="modal-body"> <div id="candidateviewModalBody" class="flex-1 overflow-y-auto p-6">
<div class="text-center py-5 text-muted"> <div class="text-center py-10 text-gray-500">
<i class="fas fa-spinner fa-spin fa-2x"></i><br> <i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
{% trans "Loading content..." %} <p>{% trans "Loading application data..." %}</p>
</div>
</div>
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
<button type="button" class="w-full px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeCandidateModal()">
{% trans "Close" %}
</button>
</div>
</div>
</div>
</div>
<!-- Email Modal -->
<div id="emailModal" class="fixed inset-0 z-50 hidden" aria-labelledby="emailModalLabel" role="dialog" aria-modal="true">
<!-- Backdrop -->
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeEmailModal()"></div>
<!-- Modal Content -->
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<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="emailModalLabel">
<i data-lucide="mail" class="w-5 h-5 inline mr-2"></i>{% trans "Compose Email" %}
</h5>
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeEmailModal()" aria-label="Close">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<div id="emailModalBody" class="flex-1 overflow-y-auto p-6">
<div class="text-center py-10 text-gray-500">
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
<p>{% trans "Loading email form..." %}</p>
</div> </div>
</div> </div>
</div> </div>
@ -440,293 +261,142 @@
</div> </div>
<!-- Sync Results Modal --> <!-- Sync Results Modal -->
<div class="modal fade" id="syncResultsModal" tabindex="-1" aria-labelledby="syncResultsModalLabel" aria-hidden="true"> <div id="syncResultsModal" class="fixed inset-0 z-50 hidden" aria-labelledby="syncResultsModalLabel" role="dialog" aria-modal="true">
<div class="modal-dialog modal-lg"> <!-- Backdrop -->
<div class="modal-content kaauh-card"> <div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeSyncResultsModal()"></div>
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="syncResultsModalLabel" style="color: var(--kaauh-teal-dark);"> <!-- Modal Content -->
<i class="fas fa-sync me-2"></i>{% trans "Sync Results" %} <div class="relative z-10 flex min-h-screen items-center justify-center p-4">
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
<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="syncResultsModalLabel">
<i data-lucide="refresh-ccw" class="w-5 h-5 inline mr-2"></i>{% trans "Sync Results" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeSyncResultsModal()" aria-label="Close">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div> </div>
<div id="syncResultsModalBody" class="modal-body"> <div id="syncResultsModalBody" class="flex-1 overflow-y-auto p-6">
<div class="text-center py-5 text-muted"> <div class="text-center py-10 text-gray-500">
<i class="fas fa-spinner fa-spin fa-2x"></i><br> <i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
{% trans "Syncing applications..." %} <p>{% trans "Syncing applications..." %}</p>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button> <button type="button" class="w-full px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeSyncResultsModal()">
{% trans "Close" %}
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Email Modal -->
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content kaauh-card">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
<h5 class="modal-title" id="emailModalLabel" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-envelope me-2"></i>{% trans "Compose Email" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div id="emailModalBody" class="modal-body">
<div class="text-center py-5 text-muted">
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
{% trans "Loading email form..." %}
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block customJS %} {% block customJS %}
<script> <script>
// Reinitialize Lucide icons after content loads
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const selectAllCheckbox = document.getElementById('selectAllCheckbox'); if (typeof lucide !== 'undefined') {
const rowCheckboxes = document.querySelectorAll('.rowCheckbox'); lucide.createIcons();
if (selectAllCheckbox) {
// Function to safely update the header checkbox state
function updateSelectAllState() {
const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
const totalCount = rowCheckboxes.length;
if (checkedCount === 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
} else if (checkedCount === totalCount) {
selectAllCheckbox.checked = true;
selectAllCheckbox.indeterminate = false;
} else {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = true;
}
}
// 1. Logic for the 'Select All' checkbox (Clicking it updates all rows)
selectAllCheckbox.addEventListener('change', function () {
const isChecked = selectAllCheckbox.checked;
rowCheckboxes.forEach(checkbox => checkbox.removeEventListener('change', updateSelectAllState));
rowCheckboxes.forEach(function (checkbox) {
checkbox.checked = isChecked;
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
});
rowCheckboxes.forEach(checkbox => checkbox.addEventListener('change', updateSelectAllState));
updateSelectAllState();
});
// 2. Logic to update 'Select All' state based on row checkboxes
rowCheckboxes.forEach(function (checkbox) {
checkbox.addEventListener('change', updateSelectAllState);
});
// Initial check to set the correct state on load (in case items are pre-checked)
updateSelectAllState();
} }
}); });
function syncHiredCandidates() { // ========================================
const syncButton = document.querySelector('[onclick="syncHiredCandidates()"]'); // Modal Functions
const modal = new bootstrap.Modal(document.getElementById('syncResultsModal')); // ========================================
// Show modal with loading state function openCandidateModal(url) {
document.getElementById('syncResultsModalBody').innerHTML = ` const modal = document.getElementById('candidateviewModal');
<div class="text-center py-5"> const modalBody = document.getElementById('candidateviewModalBody');
<div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">Loading...</span> // Reset content
</div> modalBody.innerHTML = `
<h5>{% trans "Syncing hired applications..." %}</h5> <div class="text-center py-10 text-gray-500">
<p class="text-muted">{% trans "Please wait while we sync applications to external sources." %}</p> <i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
<p>{% trans "Loading application data..." %}</p>
</div> </div>
`; `;
modal.show(); // Show modal
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// Disable sync button during sync // Load content via HTMX
syncButton.disabled = true; if (url && typeof htmx !== 'undefined') {
syncButton.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i> {% trans "Syncing..." %}'; htmx.ajax('GET', url, {target: '#candidateviewModalBody', swap: 'innerHTML'});
// Perform sync request
fetch(`{% url 'sync_hired_applications' job.slug %}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'queued') {
// Task is queued, start polling for status
console.log('Sync task queued with ID:', data.task_id);
pollSyncStatus(data.task_id);
} else if (data.status === 'success') {
displaySyncResults(data.results);
} else {
displaySyncError(data.message);
}
})
.catch(error => {
console.error('Sync error:', error);
displaySyncError('{% trans "An unexpected error occurred during sync." %}');
})
.finally(() => {
// Re-enable sync button
syncButton.disabled = false;
syncButton.innerHTML = '<i class="fas fa-sync me-1"></i> {% trans "Sync to Sources" %}';
});
} }
function displaySyncResults(results) { // Reinitialize icons
const modalBody = document.getElementById('syncResultsModalBody');
console.log('Sync results:', results);
let html = '<div class="sync-results">';
// Summary section
html += `
<div class="alert alert-info mb-4">
<h6 class="alert-heading">{% trans "Sync Summary" %}</h6>
<div class="row">
<div class="col-md-3">
<strong>{% trans "Total Sources:" %}</strong> ${results.source_results?.total_sources}
</div>
<div class="col-md-3">
<strong>{% trans "Successful:" %}</strong> <span class="text-success">${results.successful_syncs}</span>
</div>
<div class="col-md-3">
<strong>{% trans "Failed:" %}</strong> <span class="text-danger">${results.failed_syncs}</span>
</div>
<div class="col-md-3">
<strong>{% trans "Applications Synced:" %}</strong> ${results.total_candidates}
</div>
</div>
</div>
`;
// Detailed results for each source
if (results.sources && results.sources.length > 0) {
html += '<h6 class="mb-3">{% trans "Source Details" %}</h6>';
results.sources.forEach(source => {
const statusClass = source.status === 'success' ? 'success' : 'danger';
const statusIcon = source.status === 'success' ? 'check-circle' : 'exclamation-triangle';
html += `
<div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>${source.source_name}</strong>
<span class="badge bg-${statusClass}">
<i class="fas fa-${statusIcon} me-1"></i>
${source.status.toUpperCase()}
</span>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<small class="text-muted">{% trans "Applications Processed:" %}</small>
<div class="fw-bold">${source.candidates_processed}</div>
</div>
<div class="col-md-6">
<small class="text-muted">{% trans "Duration:" %}</small>
<div class="fw-bold">${source.duration}</div>
</div>
</div>
${source.message ? `<div class="mt-2"><small class="text-muted">{% trans "Message:" %}</small><div>${source.message}</div></div>` : ''}
${source.error ? `<div class="mt-2 text-danger"><small>{% trans "Error:" %}</small><div>${source.error}</div></div>` : ''}
</div>
</div>
`;
});
}
html += '</div>';
modalBody.innerHTML = html;
}
function pollSyncStatus(taskId) {
console.log('Polling for sync status...');
const pollInterval = setInterval(() => {
fetch(`/sync/task/${taskId}/status/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
}
})
.then(response => response.json())
.then(data => {
if (data.status === 'completed') {
clearInterval(pollInterval);
displaySyncResults(data.result);
} else if (data.status === 'failed') {
clearInterval(pollInterval);
displaySyncError(data.message || '{% trans "Sync task failed" %}');
} else if (data.status === 'running') {
updateSyncProgress(data.message);
}
// For 'pending' status, continue polling
})
.catch(error => {
console.error('Polling error:', error);
clearInterval(pollInterval);
displaySyncError('{% trans "Failed to check sync status" %}');
});
}, 2000); // Poll every 2 seconds
// Set a timeout to stop polling after 5 minutes
setTimeout(() => { setTimeout(() => {
clearInterval(pollInterval); if (typeof lucide !== 'undefined') lucide.createIcons();
displaySyncError('{% trans "Sync timed out after 5 minutes" %}'); }, 100);
}, 300000);
} }
function updateSyncProgress(message) { function closeCandidateModal() {
const modalBody = document.getElementById('syncResultsModalBody'); const modal = document.getElementById('candidateviewModal');
modal.classList.add('hidden');
document.body.style.overflow = '';
}
function openEmailModal() {
const modal = document.getElementById('emailModal');
const modalBody = document.getElementById('emailModalBody');
const applicationForm = document.getElementById('application-form');
const url = '{% url "compose_application_email" job.slug %}';
// Reset content
modalBody.innerHTML = ` modalBody.innerHTML = `
<div class="text-center py-5"> <div class="text-center py-10 text-gray-500">
<div class="spinner-border text-primary mb-3" role="status"> <i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
<span class="visually-hidden">Loading...</span> <p>{% trans "Loading email form..." %}</p>
</div>
<h5>{% trans "Sync in progress..." %}</h5>
<p class="text-muted">${message}</p>
</div> </div>
`; `;
// Show modal
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
// Load content via HTMX with form data
if (url && typeof htmx !== 'undefined' && applicationForm) {
htmx.ajax('GET', url, {
target: '#emailModalBody',
swap: 'innerHTML',
values: htmx.values(applicationForm)
});
} }
function displaySyncError(message) { // Reinitialize icons
const modalBody = document.getElementById('syncResultsModalBody'); setTimeout(() => {
modalBody.innerHTML = ` if (typeof lucide !== 'undefined') lucide.createIcons();
<div class="alert alert-danger text-center"> }, 100);
<i class="fas fa-exclamation-triangle fa-3x mb-3"></i>
<h5>{% trans "Sync Failed" %}</h5>
<p>${message}</p>
</div>
`;
} }
// Helper function to get CSRF token function closeEmailModal() {
function getCookie(name) { const modal = document.getElementById('emailModal');
let cookieValue = null; modal.classList.add('hidden');
if (document.cookie && document.cookie !== '') { document.body.style.overflow = '';
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
} }
function openSyncResultsModal() {
const modal = document.getElementById('syncResultsModal');
modal.classList.remove('hidden');
document.body.style.overflow = 'hidden';
} }
function closeSyncResultsModal() {
const modal = document.getElementById('syncResultsModal');
modal.classList.add('hidden');
document.body.style.overflow = '';
} }
return cookieValue;
// Close modals on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeCandidateModal();
closeEmailModal();
closeSyncResultsModal();
} }
});
</script> </script>
{% endblock %} {% endblock %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,236 +3,72 @@
{% block title %}{% trans "Delete Applicant" %} - {{ block.super }}{% endblock %} {% block title %}{% trans "Delete Applicant" %} - {{ block.super }}{% endblock %}
{% block customCSS %}
<style>
/* KAAT-S UI Variables */
:root {
--kaauh-teal: #00636e;
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-primary-text: #343a40;
--kaauh-danger: #dc3545;
--kaauh-warning: #ffc107;
}
/* Main Container & Card Styling */
.kaauh-card {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
background-color: white;
}
/* Warning Section */
.warning-section {
background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%);
border: 1px solid #ffeeba;
border-radius: 0.75rem;
padding: 2rem;
margin-bottom: 2rem;
text-align: center;
}
.warning-icon {
font-size: 4rem;
color: var(--kaauh-warning);
margin-bottom: 1rem;
}
.warning-title {
color: #856404;
font-weight: 700;
margin-bottom: 1rem;
}
.warning-text {
color: #856404;
margin-bottom: 0;
}
/* Candidate Info Card */
.candidate-info {
background-color: #f8f9fa;
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 2rem;
border: 1px solid var(--kaauh-border);
}
.info-item {
display: flex;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid #e9ecef;
}
.info-item:last-child {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
.info-icon {
width: 40px;
height: 40px;
background-color: var(--kaauh-teal);
color: white;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
margin-right: 1rem;
flex-shrink: 0;
}
.info-content {
flex: 1;
}
.info-label {
font-weight: 600;
color: var(--kaauh-primary-text);
margin-bottom: 0.25rem;
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.info-value {
color: #6c757d;
font-size: 1rem;
}
/* Button Styling */
.btn-danger {
background-color: var(--kaauh-danger);
border-color: var(--kaauh-danger);
color: white;
font-weight: 600;
transition: all 0.2s ease;
}
.btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
color: white;
font-weight: 600;
}
/* Consequence List */
.consequence-list {
list-style: none;
padding: 0;
margin: 0;
}
.consequence-list li {
padding: 0.5rem 0;
border-bottom: 1px solid #e9ecef;
color: #6c757d;
}
.consequence-list li:last-child {
border-bottom: none;
}
.consequence-list li i {
color: var(--kaauh-danger);
margin-right: 0.5rem;
}
/* Candidate Profile Image */
.candidate-avatar {
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
border: 3px solid var(--kaauh-teal);
}
.avatar-placeholder {
width: 80px;
height: 80px;
border-radius: 50%;
background-color: #e9ecef;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid var(--kaauh-teal);
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container mx-auto px-4 py-8">
<!-- Header Section --> <!-- 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">
<div> <div>
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h1 class="text-3xl font-bold text-temple-red mb-2 flex items-center gap-3">
<i class="fas fa-exclamation-triangle me-2"></i> <div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="alert-triangle" class="w-8 h-8 text-temple-red"></i>
</div>
{% trans "Delete Applicant" %} {% trans "Delete Applicant" %}
</h1> </h1>
<p class="text-muted mb-0"> <p class="text-gray-600">
{% trans "You are about to delete an applicant's application. This action cannot be undone." %} {% trans "You are about to delete an applicant's application. This action cannot be undone." %}
</p> </p>
</div> </div>
<a href="{% url 'candidate_detail' object.slug %}" class="btn btn-secondary"> <a href="{% url 'candidate_detail' object.slug %}" class="inline-flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-6 py-2.5 rounded-xl transition">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Applicant" %} <i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Applicant" %}
</a> </a>
</div> </div>
<div class="row justify-content-center"> <div class="flex justify-center">
<div class="col-lg-8"> <div class="w-full max-w-4xl">
<!-- Warning Section --> <!-- Warning Section -->
<div class="warning-section"> <div class="bg-gradient-to-br from-yellow-100 to-amber-50 border border-yellow-200 rounded-2xl p-8 mb-6 text-center">
<div class="warning-icon"> <div class="bg-temple-red/10 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-4">
<i class="fas fa-exclamation-triangle"></i> <i data-lucide="alert-triangle" class="w-12 h-12 text-temple-red"></i>
</div> </div>
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3> <h3 class="text-2xl font-bold text-amber-800 mb-3">{% trans "Warning: This action cannot be undone!" %}</h3>
<p class="warning-text"> <p class="text-amber-700">
{% trans "Deleting this applicant will permanently remove all associated data. Please review the information below carefully before proceeding." %} {% trans "Deleting this applicant will permanently remove all associated data. Please review information below carefully before proceeding." %}
</p> </p>
</div> </div>
<!-- Candidate Information --> <!-- Applicant Info Card -->
<div class="card kaauh-card mb-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
<div class="card-header bg-white border-bottom"> <div class="bg-white border-b border-gray-200 p-5">
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);"> <h5 class="text-lg font-bold text-temple-red flex items-center gap-2">
<i class="fas fa-user me-2"></i> <i data-lucide="user" class="w-5 h-5"></i>
{% trans "Applicant to be Deleted" %} {% trans "Applicant to be Deleted" %}
</h5> </h5>
</div> </div>
<div class="card-body"> <div class="p-6">
<div class="candidate-info"> <div class="bg-gray-50 rounded-xl p-6 mb-6">
<div class="d-flex align-items-center mb-4"> <div class="flex items-center gap-4 mb-5 pb-5 border-b border-gray-200">
{% if object.profile_image %} {% if object.profile_image %}
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}" class="candidate-avatar me-3"> <img src="{{ object.profile_image.url }}" alt="{{ object.name }}" class="w-20 h-20 rounded-full object-cover border-3 border-temple-red">
{% else %} {% else %}
<div class="avatar-placeholder me-3"> <div class="w-20 h-20 rounded-full bg-gray-200 flex items-center justify-center border-3 border-temple-red">
<i class="fas fa-user text-muted fa-2x"></i> <i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
</div> </div>
{% endif %} {% endif %}
<div> <div>
<h4 class="mb-1">{{ object.name }}</h4> <h4 class="text-xl font-bold text-gray-900 mb-1">{{ object.name }}</h4>
{% if object.email %} {% if object.email %}
<p class="text-muted mb-0">{{ object.email }}</p> <p class="text-gray-600">{{ object.email }}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="info-item"> <div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
<div class="info-icon"> <div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
<i class="fas fa-briefcase"></i> <i data-lucide="briefcase" class="w-5 h-5"></i>
</div> </div>
<div class="info-content"> <div class="flex-1">
<div class="info-label">{% trans "Position Applied" %}</div> <div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Position Applied" %}</div>
<div class="info-value"> <div class="text-gray-900 text-base">
{% if object.job_posting %} {% if object.job_posting %}
{{ object.job_posting.title }} {{ object.job_posting.title }}
{% else %} {% else %}
@ -243,36 +79,38 @@
</div> </div>
{% if object.phone %} {% if object.phone %}
<div class="info-item"> <div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
<div class="info-icon"> <div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
<i class="fas fa-phone"></i> <i data-lucide="phone" class="w-5 h-5"></i>
</div> </div>
<div class="info-content"> <div class="flex-1">
<div class="info-label">{% trans "Phone" %}</div> <div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Phone" %}</div>
<div class="info-value">{{ object.phone }}</div> <div class="text-gray-900 text-base">{{ object.phone }}</div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
<div class="info-item"> <div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
<div class="info-icon"> <div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
<i class="fas fa-calendar"></i> <i data-lucide="calendar" class="w-5 h-5"></i>
</div> </div>
<div class="info-content"> <div class="flex-1">
<div class="info-label">{% trans "Applied On" %}</div> <div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Applied On" %}</div>
<div class="info-value">{{ object.created_at|date:"F d, Y" }}</div> <div class="text-gray-900 text-base">{{ object.created_at|date:"F d, Y" }}</div>
</div> </div>
</div> </div>
<div class="info-item"> <div class="flex items-start gap-4">
<div class="info-icon"> <div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
<i class="fas fa-info-circle"></i> <i data-lucide="info" class="w-5 h-5"></i>
</div> </div>
<div class="info-content"> <div class="flex-1">
<div class="info-label">{% trans "Status" %}</div> <div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Status" %}</div>
<div class="info-value"> <div class="text-gray-900 text-base">
{% if object.status %} {% if object.status %}
<span class="badge bg-info">{{ object.get_status_display }}</span> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
{{ object.get_status_display }}
</span>
{% else %} {% else %}
{% trans "Not specified" %} {% trans "Not specified" %}
{% endif %} {% endif %}
@ -283,34 +121,34 @@
</div> </div>
</div> </div>
<!-- Consequences --> <!-- Consequences Card -->
<div class="card kaauh-card mb-4"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
<div class="card-header bg-white border-bottom"> <div class="bg-white border-b border-gray-200 p-5">
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);"> <h5 class="text-lg font-bold text-temple-red flex items-center gap-2">
<i class="fas fa-list me-2"></i> <i data-lucide="list" class="w-5 h-5"></i>
{% trans "What will happen when you delete this applicant?" %} {% trans "What will happen when you delete this applicant?" %}
</h5> </h5>
</div> </div>
<div class="card-body"> <div class="p-6">
<ul class="consequence-list"> <ul class="space-y-3">
<li> <li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "The applicant profile and all personal information will be permanently deleted" %} {% trans "The applicant profile and all personal information will be permanently deleted" %}
</li> </li>
<li> <li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "All application data and documents will be removed" %} {% trans "All application data and documents will be removed" %}
</li> </li>
<li> <li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "Interview schedules and history will be deleted" %} {% trans "Interview schedules and history will be deleted" %}
</li> </li>
<li> <li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "Any associated notes and communications will be lost" %} {% trans "Any associated notes and communications will be lost" %}
</li> </li>
<li> <li class="flex items-start gap-2 text-gray-700">
<i class="fas fa-times-circle"></i> <i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
{% trans "This action cannot be undone under any circumstances" %} {% trans "This action cannot be undone under any circumstances" %}
</li> </li>
</ul> </ul>
@ -318,30 +156,27 @@
</div> </div>
<!-- Confirmation Form --> <!-- Confirmation Form -->
<div class="card kaauh-card"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="card-body"> <div class="p-6">
<form method="post" id="deleteForm"> <form method="post" id="deleteForm">
{% csrf_token %} {% csrf_token %}
<div class="mb-4"> <div class="mb-6">
<div class="form-check"> <label class="flex items-start gap-3 cursor-pointer">
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required> <input type="checkbox" id="confirm_delete" name="confirm_delete" required class="w-5 h-5 mt-0.5 rounded border-gray-300 text-temple-red focus:ring-temple-red focus:ring-offset-0">
<label class="form-check-label" for="confirm_delete"> <span class="text-gray-900 font-medium">
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this applicant." %}</strong> <strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this applicant." %}</strong>
</span>
</label> </label>
</div> </div>
</div>
<div class="d-flex justify-content-between"> <div class="flex flex-col sm:flex-row gap-3 justify-between">
<a href="{% url 'candidate_detail' object.slug %}" class="btn btn-secondary btn-lg"> <a href="{% url 'candidate_detail' object.slug %}" class="inline-flex items-center justify-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-8 py-3 rounded-xl transition">
<i class="fas fa-times me-2"></i> <i data-lucide="x" class="w-5 h-5"></i>
{% trans "Cancel" %} {% trans "Cancel" %}
</a> </a>
<button type="submit" <button type="submit" id="deleteButton" disabled class="inline-flex items-center justify-center gap-2 bg-red-500 hover:bg-red-600 text-white font-semibold px-8 py-3 rounded-xl transition shadow-sm hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed">
class="btn btn-danger btn-lg" <i data-lucide="trash-2" class="w-5 h-5"></i>
id="deleteButton"
disabled>
<i class="fas fa-trash me-2"></i>
{% trans "Delete Applicant Permanently" %} {% trans "Delete Applicant Permanently" %}
</button> </button>
</div> </div>
@ -361,26 +196,12 @@ document.addEventListener('DOMContentLoaded', function() {
function validateForm() { function validateForm() {
const checkboxChecked = confirmDeleteCheckbox.checked; const checkboxChecked = confirmDeleteCheckbox.checked;
deleteButton.disabled = !checkboxChecked; deleteButton.disabled = !checkboxChecked;
if (checkboxChecked) {
deleteButton.classList.remove('btn-secondary');
deleteButton.classList.add('btn-danger');
} else {
deleteButton.classList.remove('btn-danger');
deleteButton.classList.add('btn-secondary');
}
} }
confirmDeleteCheckbox.addEventListener('change', validateForm); confirmDeleteCheckbox.addEventListener('change', validateForm);
validateForm();
// Add confirmation before final submission lucide.createIcons();
/*deleteForm.addEventListener('submit', function(e) {
const confirmMessage = "{% trans 'Are you absolutely sure you want to delete this candidate? This action cannot be undone.' %}";
if (!confirm(confirmMessage)) {
e.preventDefault();
}
});
*/
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,33 +1,64 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n %}
{% block title %} {% trans "Interview Calendar" %} {% endblock %} {% block title %} {% trans "Interview Calendar" %} {% endblock %}
{% block customCSS %} {% block customCSS %}
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css" rel="stylesheet">
<style> <style>
:root { :root {
--calendar-color: #00636e; --calendar-color: #9d2235;
--calendar-light: rgba(0, 99, 110, 0.1); --calendar-light: rgba(157, 34, 53, 0.1);
--calendar-hover: rgba(0, 99, 110, 0.2);
} }
.calendar-container { .calendar-container {
background-color: white; background-color: white;
border-radius: 0.75rem; border-radius: 1rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
padding: 1.5rem; padding: 1.5rem;
border: 1px solid var(--kaauh-border); border: 1px solid #e5e7eb;
} }
.fc-toolbar-title { .fc-toolbar-title {
color: var(--calendar-color) !important; color: #9d2235 !important;
font-weight: 700 !important; font-weight: 700 !important;
font-size: 1.5rem !important;
} }
.fc-button-primary { .fc-button-primary {
background-color: var(--calendar-color) !important; background-color: #9d2235 !important;
border-color: var(--calendar-color) !important; border-color: #9d2235 !important;
text-transform: capitalize; text-transform: capitalize;
font-weight: 600 !important;
padding: 0.5rem 1rem !important;
}
.fc-button-primary:hover {
background-color: #7a1a29 !important;
border-color: #7a1a29 !important;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(157, 34, 53, 0.3) !important;
}
.fc-button-primary:not(:disabled):active,
.fc-button-primary.fc-button-active {
background-color: #5e1320 !important;
border-color: #5e1320 !important;
}
.fc-daygrid-day.fc-day-today {
background-color: rgba(157, 34, 53, 0.1) !important;
}
.fc-daygrid-day-number {
font-weight: 600;
color: #374151;
}
.fc-col-header-cell-cushion {
font-weight: 600;
color: #374151;
padding: 0.75rem;
} }
.fc-event { .fc-event {
@ -41,6 +72,7 @@
padding: 0.25rem 0.6rem; padding: 0.25rem 0.6rem;
border-radius: 1rem; border-radius: 1rem;
font-weight: 600; font-weight: 600;
display: inline-block;
} }
.status-scheduled { background-color: #e3f2fd; color: #0d47a1; } .status-scheduled { background-color: #e3f2fd; color: #0d47a1; }
@ -53,70 +85,83 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: 1.5rem; gap: 1.5rem;
margin-top: 1.5rem; margin-top: 1.5rem;
padding: 1rem; padding: 1.25rem;
background-color: #f9fbfd; background-color: #f9fafb;
border-radius: 0.5rem; border-radius: 0.75rem;
border: 1px dashed #dee2e6; border: 1px solid #e5e7eb;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
} }
.legend-item { .legend-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.75rem;
font-size: 0.85rem; font-size: 0.875rem;
color: #495057; color: #4b5563;
font-weight: 500;
} }
.legend-color { .legend-color {
width: 12px; width: 14px;
height: 12px; height: 14px;
border-radius: 50%; border-radius: 50%;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
} }
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container mx-auto px-4 py-8">
<div class="d-flex justify-content-between align-items-center mb-4"> <!-- Header -->
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
<div> <div>
<h1 class="h3 mb-1 page-header">{% trans "Interview Calendar" %}</h1> <h1 class="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
<p class="text-muted mb-0">{{ job.title|default:"Global Schedule" }}</p> <div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="calendar" class="w-8 h-8 text-temple-red"></i>
</div> </div>
<div class="text-center"> {% trans "Interview Calendar" %}
<p class="text-muted small mb-0">Tenhal | تنحل</p> </h1>
<p class="text-gray-600">{{ job.title|default:"Global Schedule" }}</p>
</div> </div>
</div> </div>
<div class="calendar-container"> <!-- Calendar -->
<div class="calendar-container mb-6">
<div id="calendar"></div> <div id="calendar"></div>
<div class="calendar-legend"> <div class="calendar-legend">
<div class="legend-item"> <div class="legend-item">
<div class="legend-color" style="background-color: #00636e;"></div> <div class="legend-color" style="background-color: #00636e; box-shadow: 0 0 8px rgba(0, 99, 110, 0.4);"></div>
<span>{% trans "Scheduled" %}</span> <span>{% trans "Scheduled" %}</span>
</div> </div>
<div class="legend-item"> <div class="legend-item">
<div class="legend-color" style="background-color: #00a86b;"></div> <div class="legend-color" style="background-color: #00a86b; box-shadow: 0 0 8px rgba(0, 168, 107, 0.4);"></div>
<span>{% trans "Confirmed" %}</span> <span>{% trans "Confirmed" %}</span>
</div> </div>
<div class="legend-item"> <div class="legend-item">
<div class="legend-color" style="background-color: #e74c3c;"></div> <div class="legend-color" style="background-color: #e74c3c; box-shadow: 0 0 8px rgba(231, 76, 60, 0.4);"></div>
<span>{% trans "Cancelled" %}</span> <span>{% trans "Cancelled" %}</span>
</div> </div>
<div class="legend-item"> <div class="legend-item">
<div class="legend-color" style="background-color: #95a5a6;"></div> <div class="legend-color" style="background-color: #95a5a6; box-shadow: 0 0 8px rgba(149, 165, 166, 0.4);"></div>
<span>{% trans "Completed" %}</span> <span>{% trans "Completed" %}</span>
</div> </div>
</div> </div>
</div> </div>
<div class="interview-details mt-4" id="interview-details" style="display: none;"> <!-- Interview Details -->
<div class="card shadow-sm border-start border-4 border-info"> <div class="interview-details hidden" id="interview-details">
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<h5 class="mb-0 text-primary-theme"><i class="fas fa-info-circle me-2"></i>{% trans "Interview Details" %}</h5> <div class="p-6 border-b border-gray-200 flex justify-between items-center bg-white">
<button type="button" class="btn-close" id="close-details"></button> <h5 class="text-xl font-bold text-temple-red flex items-center gap-2">
<i data-lucide="info" class="w-5 h-5"></i>
{% trans "Interview Details" %}
</h5>
<button type="button" id="close-details" class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div> </div>
<div class="card-body" id="interview-info"> <div class="p-6" id="interview-info">
</div> </div>
</div> </div>
</div> </div>
@ -129,7 +174,6 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
var calendarEl = document.getElementById('calendar'); var calendarEl = document.getElementById('calendar');
// FIX: Parse the JSON script to avoid "None is not defined" error
const eventsData = JSON.parse(document.getElementById('calendar-events-data').textContent); const eventsData = JSON.parse(document.getElementById('calendar-events-data').textContent);
var calendar = new FullCalendar.Calendar(calendarEl, { var calendar = new FullCalendar.Calendar(calendarEl, {
@ -160,45 +204,49 @@ document.addEventListener('DOMContentLoaded', function() {
const statusText = status.charAt(0).toUpperCase() + status.slice(1); const statusText = status.charAt(0).toUpperCase() + status.slice(1);
let meetingInfo = ''; let meetingInfo = '';
// FIX: Corrected translation tags and property checks
if (event.extendedProps.meeting_id) { if (event.extendedProps.meeting_id) {
meetingInfo = ` meetingInfo = `
<div class="mt-3 p-3 bg-light rounded border"> <div class="mt-3 p-3 bg-gray-50 rounded-lg border border-gray-200">
<h6 class="text-primary-theme"><i class="fas fa-video me-2"></i>{% trans "Meeting Information" %}</h6> <h6 class="text-temple-red font-bold mb-2 flex items-center gap-2">
<p class="mb-1"><strong>{% trans "Meeting ID:" %}</strong> ${event.extendedProps.meeting_id}</p> <i data-lucide="video" class="w-4 h-4"></i>
<p class="mb-0"><strong>{% trans "Join URL:" %}</strong> <a href="${event.extendedProps.join_url}" target="_blank" class="text-break">${event.extendedProps.join_url}</a></p> {% trans "Meeting Information" %}
</h6>
<p class="mb-1 text-sm"><strong>{% trans "Meeting ID:" %}</strong> ${event.extendedProps.meeting_id}</p>
<p class="mb-0 text-sm"><strong>{% trans "Join URL:" %}</strong> <a href="${event.extendedProps.join_url}" target="_blank" class="text-temple-red hover:underline break-all">${event.extendedProps.join_url}</a></p>
</div> </div>
`; `;
} }
infoContainer.innerHTML = ` infoContainer.innerHTML = `
<div class="row"> <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="col-md-6 border-end"> <div class="border-r border-gray-200 pr-0 md:pr-6">
<h6 class="text-muted text-uppercase small fw-bold">{% trans "Candidate" %}</h6> <h6 class="text-gray-500 text-xs font-bold uppercase mb-3">{% trans "Candidate" %}</h6>
<p class="mb-1"><strong>{% trans "Name:" %}</strong> ${event.extendedProps.candidate}</p> <p class="mb-2 text-sm"><strong>{% trans "Name:" %}</strong> ${event.extendedProps.candidate}</p>
<p><strong>{% trans "Email:" %}</strong> ${event.extendedProps.email}</p> <p class="text-sm"><strong>{% trans "Email:" %}</strong> ${event.extendedProps.email}</p>
</div> </div>
<div class="col-md-6"> <div>
<h6 class="text-muted text-uppercase small fw-bold">{% trans "Schedule" %}</h6> <h6 class="text-gray-500 text-xs font-bold uppercase mb-3">{% trans "Schedule" %}</h6>
<p class="mb-1"><strong>{% trans "Date:" %}</strong> ${event.start.toLocaleDateString()}</p> <p class="mb-2 text-sm"><strong>{% trans "Date:" %}</strong> ${event.start.toLocaleDateString()}</p>
<p class="mb-1"><strong>{% trans "Time:" %}</strong> ${event.start.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</p> <p class="mb-2 text-sm"><strong>{% trans "Time:" %}</strong> ${event.start.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</p>
<p><strong>{% trans "Status:" %}</strong> <span class="status-badge ${statusClass}">${statusText}</span></p> <p class="text-sm"><strong>{% trans "Status:" %}</strong> <span class="status-badge ${statusClass}">${statusText}</span></p>
</div> </div>
</div> </div>
${meetingInfo} ${meetingInfo}
<div class="mt-4 pt-3 border-top"> <div class="mt-4 pt-3 border-t border-gray-200">
<a href="${event.url}" class="btn btn-main-action btn-sm"> <a href="${event.url}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition shadow-sm hover:shadow-md">
<i class="fas fa-external-link-alt me-1"></i> {% trans "View Full Application" %} <i data-lucide="external-link" class="w-4 h-4"></i>
{% trans "View Full Application" %}
</a> </a>
</div> </div>
`; `;
detailsContainer.style.display = 'block'; detailsContainer.classList.remove('hidden');
detailsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' }); detailsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
lucide.createIcons();
} }
document.getElementById('close-details').addEventListener('click', function() { document.getElementById('close-details').addEventListener('click', function() {
document.getElementById('interview-details').style.display = 'none'; document.getElementById('interview-details').classList.add('hidden');
}); });
}); });
</script> </script>

View File

@ -1,144 +1,130 @@
<!-- templates/recruitment/interview_detail.html -->
{% extends "base.html" %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n %}
{% block customCSS %} {% block title %}{% trans "Interview Details" %} - {{ block.super }}{% endblock %}
<style>
:root {
--calendar-color: #00636e;
}
.detail-header {
background-color: var(--calendar-color);
color: white;
padding: 1rem;
border-radius: 0.25rem;
margin-bottom: 1rem;
}
.detail-card {
border-left: 4px solid var(--calendar-color);
}
.status-badge {
font-size: 0.875rem;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
}
.status-scheduled {
background-color: #e3f2fd;
color: #0d47a1;
}
.status-confirmed {
background-color: #e8f5e9;
color: #1b5e20;
}
.status-cancelled {
background-color: #ffebee;
color: #b71c1c;
}
.status-completed {
background-color: #f5f5f5;
color: #424242;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<div class="container mt-4"> <div class="px-4 py-6">
<div class="detail-header"> <!-- Header -->
<div class="d-flex justify-content-between align-items-center"> <div class="bg-gradient-to-br from-temple-red to-red-800 rounded-xl shadow-xl p-6 mb-6 text-white">
<h1 class="h3 mb-0">{% trans "Interview Details" %}</h1> <div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
<div> <div class="flex-1">
<span class="h5">{{ job.title }}</span> <h1 class="text-3xl font-bold mb-2 flex items-center gap-2">
<i data-lucide="calendar-check" class="w-8 h-8"></i>
{% trans "Interview Details" %}
</h1>
<p class="text-red-100 text-lg">{{ job.title }}</p>
</div> </div>
</div> </div>
</div> </div>
<div class="card detail-card mb-4"> <!-- Details Card -->
<div class="card-body"> <div class="bg-white rounded-xl shadow-md border-l-4 border-temple-red overflow-hidden mb-6">
<div class="row"> <div class="p-6">
<div class="col-md-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<h5>{% trans "Applicant Information"%}</h5> <!-- Applicant Information -->
<table class="table table-borderless"> <div class="bg-gray-50 rounded-lg p-5">
<tr> <h3 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
<td><strong>{% trans "Name:" %}</strong></td> <i data-lucide="user" class="w-5 h-5"></i>
<td>{{ interview.candidate.name }}</td> {% trans "Applicant Information" %}
</tr> </h3>
<tr> <div class="space-y-3">
<td><strong>{% trans "Email:" %}</strong></td> <div class="flex">
<td>{{ interview.candidate.email }}</td> <span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Name:" %}</span>
</tr> <span class="text-gray-900">{{ interview.candidate.name }}</span>
<tr>
<td><strong>{% trans "Phone:" %}</strong></td>
<td>{{ interview.candidate.phone|default:"Not provided" }}</td>
</tr>
</table>
</div> </div>
<div class="col-md-6"> <div class="flex">
<h5>{% trans "Interview Details" %}</h5> <span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Email:" %}</span>
<table class="table table-borderless"> <span class="text-gray-900">{{ interview.candidate.email }}</span>
<tr> </div>
<td><strong>{% trans "Date:" %}</strong></td> <div class="flex">
<td>{{ interview.interview_date|date:"l, F j, Y" }}</td> <span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Phone:" %}</span>
</tr> <span class="text-gray-900">{{ interview.candidate.phone|default:"{% trans 'Not provided' %}" }}</span>
<tr> </div>
<td><strong>{% trans "Time:" %}</strong></td> </div>
<td>{{ interview.interview_time|time:"g:i A" }}</td> </div>
</tr>
<tr> <!-- Interview Details -->
<td><strong>{% trans "Status:" %}</strong></td> <div class="bg-gray-50 rounded-lg p-5">
<td> <h3 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
<span class="status-badge status-{{ interview.status }}"> <i data-lucide="clock" class="w-5 h-5"></i>
{% trans "Interview Details" %}
</h3>
<div class="space-y-3">
<div class="flex">
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Date:" %}</span>
<span class="text-gray-900">{{ interview.interview_date|date:"l, F j, Y" }}</span>
</div>
<div class="flex">
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Time:" %}</span>
<span class="text-gray-900">{{ interview.interview_time|time:"g:i A" }}</span>
</div>
<div class="flex items-center">
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Status:" %}</span>
<span class="status-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium
{% if interview.status == 'scheduled' %}bg-blue-100 text-blue-800
{% elif interview.status == 'confirmed' %}bg-green-100 text-green-800
{% elif interview.status == 'cancelled' %}bg-red-100 text-red-800
{% else %}bg-gray-100 text-gray-800{% endif %}">
{{ interview.status|title }} {{ interview.status|title }}
</span> </span>
</td> </div>
</tr> </div>
</table>
</div> </div>
</div> </div>
<!-- Meeting Information (if Zoom meeting exists) -->
{% if interview.zoom_meeting %} {% if interview.zoom_meeting %}
<div class="mt-4"> <div class="mt-6 bg-gray-50 rounded-lg p-5">
<h5>{% trans "Meeting Information" %}</h5> <h3 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
<table class="table table-borderless"> <i data-lucide="video" class="w-5 h-5"></i>
<tr> {% trans "Meeting Information" %}
<td><strong>{% trans "Meeting ID:" %}</strong></td> </h3>
<td>{{ interview.zoom_meeting.meeting_id }}</td> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
</tr> <div class="flex">
<tr> <span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Meeting ID:" %}</span>
<td><strong>{% trans "Topic:" %}</strong></td> <span class="text-gray-900 font-mono">{{ interview.zoom_meeting.meeting_id }}</span>
<td>{{ interview.zoom_meeting.topic }}</td> </div>
</tr> <div class="flex">
<tr> <span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Topic:" %}</span>
<td><strong>{% trans "Duration:" %}</strong></td> <span class="text-gray-900">{{ interview.zoom_meeting.topic }}</span>
<td>{{ interview.zoom_meeting.duration }} {% trans "minutes" %}</td> </div>
</tr> <div class="flex">
<tr> <span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Duration:" %}</span>
<td><strong>{% trans "Join URL:" %}</strong></td> <span class="text-gray-900">{{ interview.zoom_meeting.duration }} {% trans "minutes" %}</span>
<td><a href="{{ interview.zoom_meeting.join_url }}" target="_blank">{{ interview.zoom_meeting.join_url }}</a></td> </div>
</tr> <div class="flex">
</table> <span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Join URL:" %}</span>
<a href="{{ interview.zoom_meeting.join_url }}" target="_blank"
class="text-temple-red hover:text-red-700 transition font-medium">
{{ interview.zoom_meeting.join_url|truncatechars:50 }}
<i data-lucide="external-link" class="w-3 h-3 inline ml-1"></i>
</a>
</div>
</div>
</div> </div>
{% endif %} {% endif %}
<div class="mt-4"> <!-- Action Buttons -->
<div class="d-flex gap-2"> <div class="mt-6 pt-6 border-t border-gray-200">
<a href="{% url 'interview_calendar' slug=job.slug %}" class="btn btn-secondary"> <div class="flex flex-wrap gap-3">
<i class="fas fa-calendar"></i> {% trans "Back to Calendar" %} <a href="{% url 'interview_calendar' slug=job.slug %}"
class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg text-sm font-medium transition">
<i data-lucide="calendar" class="w-4 h-4"></i>
{% trans "Back to Calendar" %}
</a> </a>
{% if interview.status == 'scheduled' %} {% if interview.status == 'scheduled' %}
<button class="btn btn-success"> <button type="button"
<i class="fas fa-check"></i> {% trans "Confirm Interview" %} class="inline-flex items-center gap-2 bg-green-500 hover:bg-green-600 text-white px-4 py-2.5 rounded-lg text-sm font-medium transition">
<i data-lucide="check" class="w-4 h-4"></i>
{% trans "Confirm Interview" %}
</button> </button>
{% endif %} {% endif %}
{% if interview.status != 'cancelled' and interview.status != 'completed' %} {% if interview.status != 'cancelled' and interview.status != 'completed' %}
<button class="btn btn-danger"> <button type="button"
<i class="fas fa-times"></i> {% trans "Cancel Interview" %} class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2.5 rounded-lg text-sm font-medium transition">
<i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel Interview" %}
</button> </button>
{% endif %} {% endif %}
</div> </div>
@ -147,3 +133,12 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block customJS %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
lucide.createIcons();
});
</script>
{% endblock %}

View File

@ -1,66 +1,75 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% trans "Mark All as Read" %} - ATS{% endblock %} {% block title %}{% trans "Mark All as Read" %} - {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container mx-auto px-4 py-8">
<div class="row justify-content-center"> <div class="flex justify-center">
<div class="col-md-6"> <div class="w-full max-w-2xl">
<div class="card"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8 text-center">
<div class="card-body text-center p-5"> <div class="mb-6">
<div class="mb-4"> <div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto">
<i class="fas fa-check-double fa-3x text-success"></i> <i data-lucide="check-circle-2" class="w-12 h-12 text-green-600"></i>
</div>
</div> </div>
<h4 class="mb-3">{{ title }}</h4> <h4 class="text-2xl font-bold text-gray-900 mb-3">{{ title }}</h4>
<p class="text-muted mb-4">{{ message }}</p> <p class="text-gray-600 mb-6">{{ message }}</p>
{% if unread_count > 0 %} {% if unread_count > 0 %}
<div class="alert alert-info mb-4"> <div class="bg-blue-50 border border-blue-200 rounded-xl p-5 mb-6 text-left">
<h6 class="alert-heading"> <h6 class="text-lg font-bold text-blue-800 flex items-center gap-2 mb-3">
<i class="fas fa-info-circle me-2"></i>{% trans "What this will do" %} <i data-lucide="info" class="w-5 h-5"></i>
{% trans "What this will do" %}
</h6> </h6>
<p class="mb-2"> <p class="text-blue-700 mb-3">
{% blocktrans count count=unread_count %} {% blocktrans count count=unread_count %}
This will mark {{ count }} unread notification as read. This will mark {{ count }} unread notification as read.
{% plural %} {% plural %}
This will mark all {{ count }} unread notifications as read. This will mark all {{ count }} unread notifications as read.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<p class="mb-0"> <p class="text-blue-700">
{% trans "You can still view all notifications in your notification list, but they won't appear as unread." %} {% trans "You can still view all notifications in your notification list, but they won't appear as unread." %}
</p> </p>
</div> </div>
{% else %} {% else %}
<div class="alert alert-success mb-4"> <div class="bg-green-50 border border-green-200 rounded-xl p-5 mb-6 text-left">
<h6 class="alert-heading"> <h6 class="text-lg font-bold text-green-800 flex items-center gap-2 mb-2">
<i class="fas fa-check-circle me-2"></i>{% trans "All caught up!" %} <i data-lucide="check-circle" class="w-5 h-5"></i>
{% trans "All caught up!" %}
</h6> </h6>
<p class="mb-0"> <p class="text-green-700">
{% trans "You don't have any unread notifications to mark as read." %} {% trans "You don't have any unread notifications to mark as read." %}
</p> </p>
</div> </div>
{% endif %} {% endif %}
{% if unread_count > 0 %} {% if unread_count > 0 %}
<form method="post" class="d-inline"> <form method="post" class="inline-flex gap-3">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-success"> <button type="submit" class="inline-flex items-center gap-2 bg-green-500 hover:bg-green-600 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
<i class="fas fa-check-double me-1"></i> {% trans "Yes, Mark All as Read" %} <i data-lucide="check-circle-2" class="w-4 h-4"></i>
{% trans "Yes, Mark All as Read" %}
</button> </button>
<a href="{{ cancel_url }}" class="btn btn-outline-secondary"> <a href="{{ cancel_url }}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-6 py-2.5 rounded-xl transition">
<i class="fas fa-times me-1"></i> {% trans "Cancel" %} <i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel" %}
</a> </a>
</form> </form>
{% else %} {% else %}
<a href="{{ cancel_url }}" class="btn btn-primary"> <a href="{{ cancel_url }}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Notifications" %} <i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Back to Notifications" %}
</a> </a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<script>
lucide.createIcons();
</script>
{% endblock %} {% endblock %}

View File

@ -1,41 +1,47 @@
{% extends 'base.html' %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% trans "Delete Notification" %} - ATS{% endblock %} {% block title %}{% trans "Delete Notification" %} - {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container mx-auto px-4 py-8">
<div class="row justify-content-center"> <div class="flex justify-center">
<div class="col-md-6"> <div class="w-full max-w-2xl">
<div class="card"> <div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8 text-center">
<div class="card-body text-center p-5"> <div class="mb-6">
<div class="mb-4"> <div class="w-20 h-20 bg-amber-100 rounded-full flex items-center justify-center mx-auto">
<i class="fas fa-exclamation-triangle fa-3x text-warning"></i> <i data-lucide="alert-triangle" class="w-12 h-12 text-amber-500"></i>
</div>
</div> </div>
<h4 class="mb-3">{{ title }}</h4> <h4 class="text-2xl font-bold text-gray-900 mb-3">{{ title }}</h4>
<p class="text-muted mb-4">{{ message }}</p> <p class="text-gray-600 mb-6">{{ message }}</p>
<div class="alert alert-light mb-4"> <div class="bg-gray-50 border border-gray-200 rounded-xl p-5 mb-6 text-left">
<h6 class="alert-heading">{% trans "Notification Preview" %}</h6> <h6 class="text-lg font-bold text-gray-800 mb-3">{% trans "Notification Preview" %}</h6>
<p class="mb-2"><strong>{% trans "Message:" %}</strong> {{ notification.message|truncatewords:20 }}</p> <p class="mb-2"><strong class="text-gray-700">{% trans "Message:" %}</strong> {{ notification.message|truncatewords:20 }}</p>
<p class="mb-0"> <p>
<strong>{% trans "Created:" %}</strong> {{ notification.created_at|date:"Y-m-d H:i" }} <strong class="text-gray-700">{% trans "Created:" %}</strong> {{ notification.created_at|date:"Y-m-d H:i" }}
</p> </p>
</div> </div>
<form method="post" class="d-inline"> <form method="post" class="inline-flex gap-3">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-danger"> <button type="submit" class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
<i class="fas fa-trash me-1"></i> {% trans "Yes, Delete" %} <i data-lucide="trash-2" class="w-4 h-4"></i>
{% trans "Yes, Delete" %}
</button> </button>
<a href="{{ cancel_url }}" class="btn btn-outline-secondary"> <a href="{{ cancel_url }}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-6 py-2.5 rounded-xl transition">
<i class="fas fa-times me-1"></i> {% trans "Cancel" %} <i data-lucide="x" class="w-4 h-4"></i>
{% trans "Cancel" %}
</a> </a>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
<script>
lucide.createIcons();
</script>
{% endblock %} {% endblock %}

View File

@ -1,63 +1,78 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% trans "Notifications" %} - ATS{% endblock %} {% block title %}{% trans "Notifications" %} - {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="px-4 py-6">
<div class="d-flex justify-content-between align-items-center mb-4"> <!-- Header -->
<div> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-6">
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <div class="flex-1">
<i class="fas fa-bell me-2"></i> <h1 class="text-3xl font-bold text-temple-dark mb-1 flex items-center gap-2">
<i data-lucide="bell" class="w-8 h-8"></i>
{% trans "Notifications" %} {% trans "Notifications" %}
</h1> </h1>
<p class="text-muted mb-0"> <p class="text-gray-600">
{% blocktrans count count=total_notifications %} {% blocktrans count count=total_notifications %}
{{ count }} notification {{ count }} notification
{% plural %} {% plural %}
{{ count }} notifications {{ count }} notifications
{% endblocktrans %} {% endblocktrans %}
{% if unread_notifications %}({{ unread_notifications }} unread){% endif %} {% if unread_notifications %}
<span class="font-semibold text-temple-red">({{ unread_notifications }} unread)</span>
{% endif %}
</p> </p>
</div> </div>
<div class="d-flex gap-2"> <div class="flex gap-2">
{% if unread_notifications %} {% if unread_notifications %}
<a href="{% url 'notification_mark_all_read' %}" class="btn btn-outline-secondary"> <a href="{% url 'notification_mark_all_read' %}"
<i class="fas fa-check-double me-1"></i> {% trans "Mark All Read" %} class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
<i data-lucide="check-double" class="w-4 h-4"></i>
<span class="hidden sm:inline">{% trans "Mark All Read" %}</span>
</a> </a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<!-- Filters --> <!-- Filters -->
<div class="card mb-4"> <div class="bg-white rounded-xl shadow-md border border-gray-200 mb-6">
<div class="card-body"> <div class="p-6">
<form method="get" class="row g-3"> <form method="get" class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="col-md-3"> <div>
<label for="status_filter" class="form-label">{% trans "Status" %}</label> <label for="status_filter" class="block text-sm font-semibold text-gray-700 mb-2">
<select name="status" id="status_filter" class="form-select"> {% trans "Status" %}
</label>
<select name="status" id="status_filter"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900">
<option value="">{% trans "All Status" %}</option> <option value="">{% trans "All Status" %}</option>
<option value="unread" {% if status_filter == 'unread' %}selected{% endif %}>{% trans "Unread" %}</option> <option value="unread" {% if status_filter == 'unread' %}selected{% endif %}>{% trans "Unread" %}</option>
<option value="read" {% if status_filter == 'read' %}selected{% endif %}>{% trans "Read" %}</option> <option value="read" {% if status_filter == 'read' %}selected{% endif %}>{% trans "Read" %}</option>
<option value="sent" {% if status_filter == 'sent' %}selected{% endif %}>{% trans "Sent" %}</option> <option value="sent" {% if status_filter == 'sent' %}selected{% endif %}>{% trans "Sent" %}</option>
</select> </select>
</div> </div>
<div class="col-md-3"> <div>
<label for="type_filter" class="form-label">{% trans "Type" %}</label> <label for="type_filter" class="block text-sm font-semibold text-gray-700 mb-2">
<select name="type" id="type_filter" class="form-select"> {% trans "Type" %}
</label>
<select name="type" id="type_filter"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900">
<option value="">{% trans "All Types" %}</option> <option value="">{% trans "All Types" %}</option>
<option value="in_app" {% if type_filter == 'in_app' %}selected{% endif %}>{% trans "In-App" %}</option> <option value="in_app" {% if type_filter == 'in_app' %}selected{% endif %}>{% trans "In-App" %}</option>
<option value="email" {% if type_filter == 'email' %}selected{% endif %}>{% trans "Email" %}</option> <option value="email" {% if type_filter == 'email' %}selected{% endif %}>{% trans "Email" %}</option>
</select> </select>
</div> </div>
<div class="col-md-6"> <div>
<label class="form-label">&nbsp;</label> <label class="block text-sm font-semibold text-gray-700 mb-2">&nbsp;</label>
<div class="d-flex gap-2"> <div class="flex gap-2">
<button type="submit" class="btn btn-main-action"> <button type="submit"
<i class="fas fa-filter me-1"></i> {% trans "Filter" %} class="flex-1 bg-temple-red hover:bg-red-800 text-white font-semibold px-4 py-2.5 rounded-lg transition shadow-md hover:shadow-lg flex items-center justify-center gap-2">
<i data-lucide="filter" class="w-4 h-4"></i>
{% trans "Filter" %}
</button> </button>
<a href="{% url 'notification_list' %}" class="btn btn-outline-secondary"> <a href="{% url 'notification_list' %}"
<i class="fas fa-times me-1"></i> {% trans "Clear" %} class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg transition flex items-center justify-center gap-2">
<i data-lucide="x" class="w-4 h-4"></i>
{% trans "Clear" %}
</a> </a>
</div> </div>
</div> </div>
@ -66,81 +81,72 @@
</div> </div>
<!-- Statistics --> <!-- Statistics -->
<div class="row mb-4"> <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
<div class="col-md-4"> <div class="bg-white rounded-xl shadow-md border border-temple-red p-6 text-center">
<div class="card border-primary"> <h3 class="text-3xl font-bold text-temple-red mb-1">{{ total_notifications }}</h3>
<div class="card-body text-center"> <p class="text-gray-600">{% trans "Total Notifications" %}</p>
<h5 class="card-title text-primary">{{ total_notifications }}</h5>
<p class="card-text">{% trans "Total Notifications" %}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-warning">
<div class="card-body text-center">
<h5 class="card-title text-warning">{{ unread_notifications }}</h5>
<p class="card-text">{% trans "Unread" %}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card border-info">
<div class="card-body text-center">
<h5 class="card-title text-info">{{ email_notifications }}</h5>
<p class="card-text">{% trans "Email Notifications" %}</p>
</div> </div>
<div class="bg-white rounded-xl shadow-md border border-yellow-500 p-6 text-center">
<h3 class="text-3xl font-bold text-yellow-600 mb-1">{{ unread_notifications }}</h3>
<p class="text-gray-600">{% trans "Unread" %}</p>
</div> </div>
<div class="bg-white rounded-xl shadow-md border border-blue-500 p-6 text-center">
<h3 class="text-3xl font-bold text-blue-600 mb-1">{{ email_notifications }}</h3>
<p class="text-gray-600">{% trans "Email Notifications" %}</p>
</div> </div>
</div> </div>
<!-- Notifications List --> <!-- Notifications List -->
{% if page_obj %} {% if page_obj %}
<div class="card"> <div class="bg-white rounded-xl shadow-md border border-gray-200">
<div class="card-body p-0"> <div class="divide-y divide-gray-200">
<div class="list-group list-group-flush">
{% for notification in page_obj %} {% for notification in page_obj %}
<div class="list-group-item list-group-item-action {% if notification.status == 'PENDING' %}bg-light{% endif %}"> <div class="p-4 hover:bg-gray-50 transition {% if notification.status == 'PENDING' %}bg-blue-50{% endif %}">
<div class="d-flex justify-content-between align-items-start"> <div class="flex justify-between items-start gap-4">
<div class="flex-grow-1"> <div class="flex-1 min-w-0">
<div class="d-flex align-items-center mb-2"> <div class="flex items-center flex-wrap gap-2 mb-2">
<span class="badge bg-{{ notification.get_status_bootstrap_class }} me-2"> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
{% if notification.status == 'PENDING' %}bg-yellow-100 text-yellow-800
{% elif notification.status == 'READ' %}bg-green-100 text-green-800
{% else %}bg-gray-100 text-gray-800{% endif %}">
{{ notification.get_status_display }} {{ notification.get_status_display }}
</span> </span>
<span class="badge bg-secondary me-2"> <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-200 text-gray-800">
{{ notification.get_notification_type_display }} {{ notification.get_notification_type_display }}
</span> </span>
<small class="text-muted">{{ notification.created_at|date:"Y-m-d H:i" }}</small> <span class="text-xs text-gray-500">{{ notification.created_at|date:"Y-m-d H:i" }}</span>
</div> </div>
<h6 class="mb-1"> <h5 class="text-base font-semibold mb-1">
<a href="{% url 'notification_detail' notification.id %}" class="text-decoration-none {% if notification.status == 'PENDING' %}fw-bold{% endif %}"> <a href="{% url 'notification_detail' notification.id %}"
class="text-gray-900 hover:text-temple-red transition {% if notification.status == 'PENDING' %}font-bold{% endif %}">
{{ notification.message|truncatewords:15 }} {{ notification.message|truncatewords:15 }}
</a> </a>
</h6> </h5>
{% if notification.related_meeting %} {% if notification.related_meeting %}
<small class="text-muted"> <p class="text-sm text-gray-500">
<i class="fas fa-video me-1"></i> <i data-lucide="video" class="w-3 h-3 inline mr-1"></i>
{% trans "Related to meeting:" %} {{ notification.related_meeting.topic }} {% trans "Related to meeting:" %} {{ notification.related_meeting.topic }}
</small> </p>
{% endif %} {% endif %}
</div> </div>
<div class="d-flex flex-column gap-1"> <div class="flex flex-col gap-2">
{% if notification.status == 'PENDING' %} {% if notification.status == 'PENDING' %}
<a href="{% url 'notification_mark_read' notification.id %}" <a href="{% url 'notification_mark_read' notification.id %}"
class="btn btn-sm btn-outline-success" class="p-2 text-green-600 hover:bg-green-50 rounded-lg transition"
title="{% trans 'Mark as read' %}"> title="{% trans 'Mark as read' %}">
<i class="fas fa-check"></i> <i data-lucide="check" class="w-4 h-4"></i>
</a> </a>
{% else %} {% else %}
<a href="{% url 'notification_mark_unread' notification.id %}" <a href="{% url 'notification_mark_unread' notification.id %}"
class="btn btn-sm btn-outline-secondary" class="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition"
title="{% trans 'Mark as unread' %}"> title="{% trans 'Mark as unread' %}">
<i class="fas fa-envelope"></i> <i data-lucide="mail" class="w-4 h-4"></i>
</a> </a>
{% endif %} {% endif %}
<a href="{% url 'notification_delete' notification.id %}" <a href="{% url 'notification_delete' notification.id %}"
class="btn btn-sm btn-outline-danger" class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition"
title="{% trans 'Delete notification' %}"> title="{% trans 'Delete notification' %}">
<i class="fas fa-trash"></i> <i data-lucide="trash-2" class="w-4 h-4"></i>
</a> </a>
</div> </div>
</div> </div>
@ -148,36 +154,38 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div>
<!-- Pagination --> <!-- Pagination -->
{% if page_obj.has_other_pages %} {% if page_obj.has_other_pages %}
<nav aria-label="{% trans 'Notifications pagination' %}" class="mt-4"> <nav aria-label="{% trans 'Notifications pagination' %}" class="mt-6">
<ul class="pagination justify-content-center"> <ul class="flex justify-center items-center gap-2">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item"> <li>
<a class="page-link" href="?page={{ page_obj.previous_page_number }}&status={{ status_filter }}&type={{ type_filter }}"> <a class="px-3 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
<i class="fas fa-chevron-left"></i> href="?page={{ page_obj.previous_page_number }}&status={{ status_filter }}&type={{ type_filter }}">
<i data-lucide="chevron-left" class="w-4 h-4"></i>
</a> </a>
</li> </li>
{% endif %} {% endif %}
{% for num in page_obj.paginator.page_range %} {% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %} {% if page_obj.number == num %}
<li class="page-item active"> <li>
<span class="page-link">{{ num }}</span> <span class="px-4 py-2 bg-temple-red text-white rounded-lg font-semibold">{{ num }}</span>
</li> </li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %} {% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item"> <li>
<a class="page-link" href="?page={{ num }}&status={{ status_filter }}&type={{ type_filter }}">{{ num }}</a> <a class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
href="?page={{ num }}&status={{ status_filter }}&type={{ type_filter }}">{{ num }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="page-item"> <li>
<a class="page-link" href="?page={{ page_obj.next_page_number }}&status={{ status_filter }}&type={{ type_filter }}"> <a class="px-3 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
<i class="fas fa-chevron-right"></i> href="?page={{ page_obj.next_page_number }}&status={{ status_filter }}&type={{ type_filter }}">
<i data-lucide="chevron-right" class="w-4 h-4"></i>
</a> </a>
</li> </li>
{% endif %} {% endif %}
@ -185,10 +193,10 @@
</nav> </nav>
{% endif %} {% endif %}
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-12 bg-white rounded-xl shadow-md border border-gray-200">
<i class="fas fa-bell-slash fa-3x text-muted mb-3"></i> <i data-lucide="bell-off" class="w-16 h-16 text-gray-400 mx-auto mb-4"></i>
<h5 class="text-muted">{% trans "No notifications found" %}</h5> <h3 class="text-xl font-semibold text-gray-600 mb-2">{% trans "No notifications found" %}</h3>
<p class="text-muted"> <p class="text-gray-500 mb-4">
{% if status_filter or type_filter %} {% if status_filter or type_filter %}
{% trans "Try adjusting your filters to see more notifications." %} {% trans "Try adjusting your filters to see more notifications." %}
{% else %} {% else %}
@ -196,8 +204,10 @@
{% endif %} {% endif %}
</p> </p>
{% if status_filter or type_filter %} {% if status_filter or type_filter %}
<a href="{% url 'notification_list' %}" class="btn btn-main-action"> <a href="{% url 'notification_list' %}"
<i class="fas fa-times me-1"></i> {% trans "Clear Filters" %} class="inline-flex items-center gap-2 bg-temple-red hover:bg-red-800 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-md hover:shadow-lg">
<i data-lucide="x" class="w-4 h-4"></i>
{% trans "Clear Filters" %}
</a> </a>
{% endif %} {% endif %}
</div> </div>
@ -207,8 +217,11 @@
{% block customJS %} {% block customJS %}
<script> <script>
/*
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
lucide.createIcons();
/*
// Auto-refresh notifications every 30 seconds // Auto-refresh notifications every 30 seconds
setInterval(function() { setInterval(function() {
fetch('/api/notification-count/') fetch('/api/notification-count/')
@ -219,15 +232,15 @@ document.addEventListener('DOMContentLoaded', function() {
if (badge) { if (badge) {
badge.textContent = data.count; badge.textContent = data.count;
if (data.count > 0) { if (data.count > 0) {
badge.classList.remove('d-none'); badge.classList.remove('hidden');
} else { } else {
badge.classList.add('d-none'); badge.classList.add('hidden');
} }
} }
}) })
.catch(error => console.error('Error fetching notifications:', error)); .catch(error => console.error('Error fetching notifications:', error));
}, 30000); }, 30000);
});
*/ */
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -1,40 +1,89 @@
{% load i18n crispy_forms_tags %} {% load i18n crispy_forms_tags %}
<div class="p-3">
<div class="p-4">
<form hx-boost="true" id="noteform" action="{{ url }}" method="post" hx-select=".note-table-body" hx-target=".note-table-body" hx-swap="outerHTML" hx-push-url="false"> <form hx-boost="true" id="noteform" action="{{ url }}" method="post" hx-select=".note-table-body" hx-target=".note-table-body" hx-swap="outerHTML" hx-push-url="false">
{% csrf_token %} {% csrf_token %}
<!-- Crispy Form for rendering -->
<div class="space-y-4">
<div>
{{ form|crispy }} {{ form|crispy }}
<div class="modal-footer"> </div>
<button type="button" id="notesubmit" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
<button type="submit" class="btn btn-main-action" id="saveNoteBtn">{% trans "Save Note" %}</button> <!-- Form Actions -->
<div class="flex justify-end gap-2 pt-4 border-t-2 border-gray-200">
<button type="submit" class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium" id="saveNoteBtn">
<i data-lucide="save" class="w-4 h-4 inline mr-1"></i>
{% trans "Save Note" %}
</button>
</div>
</div> </div>
</form> </form>
<div class="table-responsive mt-3"> </div>
<table class="table table-sm" id="notesTable">
<!-- Notes Table Section -->
<div class="mt-6">
<!-- Table Header -->
<div class="bg-gray-50 border border-gray-200 rounded-xl overflow-hidden">
<table class="w-full border-collapse" id="notesTable">
<thead> <thead>
<tr> <tr class="border-b-2 border-temple-red">
<th scope="col">{% trans "Author" %}</th> <th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark">
<th scope="col" style="width: 60%;">{% trans "Note" %}</th> <i data-lucide="user" class="w-3 h-3 inline mr-1"></i>
<th scope="col">{% trans "Created" %}</th> {% trans "Author" %}
<th scope="col" class="text-end">{% trans "Actions" %}</th> </th>
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark" style="width: 60%;">
<i data-lucide="sticky-note" class="w-3 h-3 inline mr-1"></i>
{% trans "Note" %}
</th>
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark">
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
{% trans "Created" %}
</th>
<th class="px-4 py-3 text-right text-xs font-semibold text-temple-dark">
<i data-lucide="trash-2" class="w-3 h-3 inline mr-1"></i>
{% trans "Actions" %}
</th>
</tr> </tr>
</thead> </thead>
<tbody class="note-table-body"> <tbody class="note-table-body">
{% if notes %} {% if notes %}
{% for note in notes %} {% for note in notes %}
<tr id="note-{{ note.id }}"> <tr id="note-{{ note.id }}" class="hover:bg-gray-50 transition-colors border-b border-gray-200">
<td class="align-middle"> <td class="px-4 py-3">
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full bg-temple-red text-white flex items-center justify-center text-sm font-bold">
{{ note.author.first_name.0|default:note.author.username.0|upper }}
</div>
<div>
<div class="font-medium text-temple-dark text-sm">
{{ note.author.get_full_name|default:note.author.username }} {{ note.author.get_full_name|default:note.author.username }}
</div>
</div>
</div>
</td> </td>
<td class="align-middle"> <td class="px-4 py-3">
<div class="text-sm text-gray-700 bg-white p-3 rounded-lg border border-gray-200 shadow-sm">
{{ note.content|linebreaksbr }} {{ note.content|linebreaksbr }}
</div>
</td> </td>
<td class="align-middle text-nowrap"> <td class="px-4 py-3 whitespace-nowrap">
<span class="text-muted"> <div class="text-xs text-gray-500">
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
{{ note.created_at|date:"SHORT_DATETIME_FORMAT" }} {{ note.created_at|date:"SHORT_DATETIME_FORMAT" }}
</span> </div>
</td> </td>
<td class="align-middle text-end"> <td class="px-4 py-3 text-right">
<button hx-delete="{% url 'delete_note' note.slug %}" hx-target="#note-{{ note.id }}" hx-swap="delete" type="button" class="btn btn-sm btn-outline-danger delete-note-btn"> <button hx-delete="{% url 'delete_note' note.slug %}"
hx-target="#note-{{ note.id }}"
hx-swap="delete"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
hx-confirm="{% trans 'Are you sure you want to delete this note?' %}"
type="button"
class="px-3 py-1.5 border-2 border-red-500 text-red-500 rounded-lg hover:bg-red-500 hover:text-white transition text-xs font-medium delete-note-btn inline-flex items-center gap-1"
title="{% trans 'Delete Note' %}">
<i data-lucide="trash-2" class="w-3 h-3"></i>
{% trans "Delete" %} {% trans "Delete" %}
</button> </button>
</td> </td>
@ -42,10 +91,115 @@
{% endfor %} {% endfor %}
{% else %} {% else %}
<tr> <tr>
<td colspan="4" class="text-center text-muted py-3">{% trans "No notes yet." %}</td> <td colspan="4" class="text-center py-8 text-gray-500">
<div class="flex flex-col items-center gap-2">
<i data-lucide="sticky-note" class="w-12 h-12 text-gray-400"></i>
<span class="text-sm">{% trans "No notes yet." %}</span>
</div>
</td>
</tr> </tr>
{% endif %} {% endif %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<!-- Add custom styling for crispy form -->
<style>
#noteform textarea {
width: 100%;
padding:1rem 1.25rem;
background-color: white;
border: 2px solid #e5e7eb;
border-radius: 1rem;
font-size: 0.875rem;
color: #374151;
transition: all 0.3s;
resize: none;
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
}
#noteform textarea:focus {
border-color: #dc2626;
outline: none;
box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.1);
background-color: #fef2f2;
}
#noteform textarea::placeholder {
color: #9ca3af;
}
#noteform textarea:hover {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
border-color: #d1d5db;
}
#noteform label {
display: block;
font-size: 0.875rem;
font-weight: 600;
color: #1f2937;
margin-bottom: 0.5rem;
}
#noteform .help-block,
#noteform .helptext {
font-size: 0.75rem;
color: #6b7280;
margin-top: 0.5rem;
}
#noteform .text-danger,
#noteform .errorlist {
font-size: 0.75rem;
color: #dc2626;
background-color: #fef2f2;
border: 2px solid #fca5a5;
border-radius: 0.75rem;
padding: 0.75rem;
margin-top: 0.75rem;
}
</style>
<script>
// Reinitialize Lucide icons after HTMX updates
document.addEventListener('htmx:afterSwap', function(evt) {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
// Function to close the modal
function closeModal() {
const modal = document.getElementById('noteModal');
if (modal) {
const modalInstance = bootstrap.Modal.getInstance(modal);
if (modalInstance) {
modalInstance.hide();
} else {
new bootstrap.Modal(modal).hide();
}
}
}
// Cancel button click handler - attached to document to catch dynamically added buttons
document.body.addEventListener('click', function(e) {
const cancelBtn = e.target.closest('#cancelNoteBtn')
if (cancelBtn) {
e.preventDefault();
closeModal();
}
});
// Re-attach cancel button listener after HTMX updates (for the form submit cancel)
document.addEventListener('htmx:afterSwap', function(evt) {
const cancelBtn = document.getElementById('cancelNoteBtn');
if (cancelBtn) {
cancelBtn.addEventListener('click', function(e) {
e.preventDefault();
closeModal();
});
}
});
</script>

View File

@ -1,12 +1,17 @@
{% load static i18n %}
<div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true"> <div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg" role="document"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content kaauh-card"> <div class="modal-content bg-white border border-gray-200 rounded-xl shadow-lg">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="modal-header flex justify-between items-center px-6 py-4 border-b border-gray-200">
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <h5 class="modal-title text-lg font-bold text-temple-dark" id="noteModalLabel">
</div> <i data-lucide="sticky-note" class="w-5 h-5 inline mr-2"></i>{% trans "Add Note" %}
<div class="modal-body notemodal"> </h5>
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" data-bs-dismiss="modal" aria-label="Close">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div> </div>
<div class="modal-body notemodal p-6">
<!-- Content will be loaded via HTMX -->
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,32 +1,34 @@
{% load i18n %} {% load i18n %}
<div class="modal fade" id="stageConfirmationModal" tabindex="-1" aria-labelledby="stageConfirmationModalLabel" aria-hidden="true"> <div class="modal fade" id="stageConfirmationModal" tabindex="-1" aria-labelledby="stageConfirmationModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document"> <div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content kaauh-card"> <div class="modal-content bg-white border border-gray-200 rounded-xl shadow-lg">
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);"> <div class="modal-header flex justify-between items-center px-6 py-4 border-b border-gray-200">
<h5 class="modal-title" id="stageConfirmationModalLabel" style="color: var(--kaauh-teal-dark); font-weight: 700;"> <h5 class="modal-title text-lg font-bold text-temple-dark" id="stageConfirmationModalLabel">
<i class="fas fa-info-circle me-2"></i>{% trans "Confirm Stage Change" %} <i data-lucide="info" class="w-5 h-5 inline mr-2"></i>{% trans "Confirm Stage Change" %}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" data-bs-dismiss="modal" aria-label="Close">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div> </div>
<div class="modal-body"> <div class="modal-body p-6">
<div class="d-flex align-items-center justify-content-center py-3 mb-3"> <div class="flex items-center justify-center py-3 mb-3">
<i class="fas fa-exchange-alt fa-4x" style="color: var(--kaauh-teal);"></i> <i data-lucide="arrow-right-left" class="w-16 h-16 text-temple-red"></i>
</div> </div>
<p class="text-center mb-2" style="font-size: 1.1rem; color: var(--kaauh-primary-text);"> <p class="text-center mb-2 text-base text-gray-800">
<span id="stageConfirmationMessage">{% trans "Are you sure you want to change the stage?" %}</span> <span id="stageConfirmationMessage">{% trans "Are you sure you want to change the stage?" %}</span>
</p> </p>
<div class="alert alert-info text-center" role="alert"> <div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg text-center" role="alert">
<i class="fas fa-user-check me-2"></i> <i data-lucide="user-check" class="w-4 h-4 inline mr-2"></i>
<strong>{% trans "Selected Stage:" %}</strong> <span class="font-semibold">{% trans "Selected Stage:" %}</span>
<span id="targetStageName" class="fw-bold">{% trans "--" %}</span> <span id="targetStageName" class="font-bold">{% trans "--" %}</span>
</div> </div>
</div> </div>
<div class="modal-footer" style="border-top: 1px solid var(--kaauh-border);"> <div class="modal-footer px-6 py-4 border-t border-gray-200 flex justify-end gap-2">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal"> <button type="button" class="px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i>{% trans "Cancel" %} <i data-lucide="x" class="w-4 h-4 inline mr-1"></i>{% trans "Cancel" %}
</button> </button>
<button type="button" class="btn btn-main-action" id="confirmStageChangeButton"> <button type="button" class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium" id="confirmStageChangeButton">
<i class="fas fa-check me-1"></i>{% trans "Confirm" %} <i data-lucide="check" class="w-4 h-4 inline mr-1"></i>{% trans "Confirm" %}
</button> </button>
</div> </div>
</div> </div>

View File

@ -1,92 +1,106 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% trans "Schedule Meeting" %} - {{ job.title }} - ATS{% endblock %} {% block title %}{% trans "Schedule Meeting" %} - {{ job.title }} - {{ block.super }}{% endblock %}
{% block content %} {% block content %}
<div class="container py-4"> <div class="px-4 py-6 max-w-2xl mx-auto">
<div class="d-flex justify-content-between align-items-center mb-4"> <!-- Header -->
<div> <div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-6">
<h1 class="h3 mb-1"> <div class="flex-1">
<i class="fas fa-calendar-plus me-2"></i> <h1 class="text-2xl font-bold text-temple-dark mb-1 flex items-center gap-2">
<i data-lucide="calendar-plus" class="w-7 h-7"></i>
{% if has_future_meeting %} {% if has_future_meeting %}
{% trans "Update Interview" %} for {{ application.name }} {% trans "Update Interview" %} for {{ application.name }}
{% else %} {% else %}
{% trans "Schedule Interview" %} for {{ application.name }} {% trans "Schedule Interview" %} for {{ application.name }}
{% endif %} {% endif %}
</h1> </h1>
<p class="text-muted mb-0">{% trans "Job" %}: {{ job.title }}</p> <p class="text-gray-600">{% trans "Job" %}: {{ job.title }}</p>
{% if has_future_meeting %} {% if has_future_meeting %}
<div class="alert alert-info mt-2 mb-0" role="alert"> <div class="mt-3 bg-blue-50 border border-blue-200 rounded-lg p-3 flex items-start gap-2">
<i class="fas fa-info-circle me-1"></i> <i data-lucide="info" class="w-5 h-5 text-blue-500 flex-shrink-0 mt-0.5"></i>
<p class="text-sm text-blue-800">
{% trans "This application has upcoming interviews. You are updating an existing schedule." %} {% trans "This application has upcoming interviews. You are updating an existing schedule." %}
</p>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<a href="{% url 'applications_interview_view' job.slug %}" class="btn btn-outline-secondary"> <a href="{% url 'applications_interview_view' job.slug %}"
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Candidates" %} class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg text-sm font-medium transition">
<i data-lucide="arrow-left" class="w-4 h-4"></i>
{% trans "Back to Candidates" %}
</a> </a>
</div> </div>
<div class="card shadow-sm"> <!-- Form Card -->
<div class="card-body"> <div class="bg-white rounded-xl shadow-md border border-gray-200">
<form method="post"> <div class="p-6">
<form method="post" id="meetingForm">
{% csrf_token %} {% csrf_token %}
<div class="mb-3"> <!-- Topic Field -->
<label for="{{ form.topic.id_for_label }}" class="form-label"> <div class="mb-6">
<label for="{{ form.topic.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "Meeting Topic" %} {% trans "Meeting Topic" %}
</label> </label>
{{ form.topic }} {{ form.topic }}
{% if form.topic.errors %} {% if form.topic.errors %}
<div class="text-danger"> <div class="mt-1 text-sm text-red-600">
{% for error in form.topic.errors %} {% for error in form.topic.errors %}
<small>{{ error }}</small> <p>{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
<div class="form-text"> <p class="mt-1 text-sm text-gray-500">
{% trans "Default topic will be 'Interview: [Job Title] with [Candidate Name]' if left empty." %} {% trans "Default topic will be 'Interview: [Job Title] with [Candidate Name]' if left empty." %}
</div> </p>
</div> </div>
<div class="mb-3"> <!-- Start Time Field -->
<label for="{{ form.start_time.id_for_label }}" class="form-label"> <div class="mb-6">
<label for="{{ form.start_time.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "Start Time" %} {% trans "Start Time" %}
</label> </label>
{{ form.start_time }} {{ form.start_time }}
{% if form.start_time.errors %} {% if form.start_time.errors %}
<div class="text-danger"> <div class="mt-1 text-sm text-red-600">
{% for error in form.start_time.errors %} {% for error in form.start_time.errors %}
<small>{{ error }}</small> <p>{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
<div class="form-text"> <p class="mt-1 text-sm text-gray-500">
{% trans "Please select a date and time for the interview." %} {% trans "Please select a date and time for interview." %}
</div> </p>
</div> </div>
<div class="mb-4"> <!-- Duration Field -->
<label for="{{ form.duration.id_for_label }}" class="form-label"> <div class="mb-8">
<label for="{{ form.duration.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
{% trans "Duration (minutes)" %} {% trans "Duration (minutes)" %}
</label> </label>
{{ form.duration }} {{ form.duration }}
{% if form.duration.errors %} {% if form.duration.errors %}
<div class="text-danger"> <div class="mt-1 text-sm text-red-600">
{% for error in form.duration.errors %} {% for error in form.duration.errors %}
<small>{{ error }}</small> <p>{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="d-flex gap-2"> <!-- Action Buttons -->
<button type="submit" class="btn btn-primary"> <div class="flex flex-col sm:flex-row gap-3 pt-4 border-t border-gray-200">
<i class="fas fa-save me-1"></i> {% trans "Schedule Meeting" %} <button type="submit"
class="flex-1 sm:flex-none 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 justify-center gap-2">
<i data-lucide="save" class="w-5 h-5"></i>
{% trans "Schedule Meeting" %}
</button> </button>
<a href="{% url 'applications_interview_view' job.slug %}" class="btn btn-secondary"> <a href="{% url 'applications_interview_view' job.slug %}"
<i class="fas fa-times me-1"></i> {% trans "Cancel" %} class="flex-1 sm:flex-none border border-gray-300 text-gray-700 hover:bg-gray-50 px-8 py-3 rounded-xl transition flex items-center justify-center gap-2">
<i data-lucide="x" class="w-5 h-5"></i>
{% trans "Cancel" %}
</a> </a>
</div> </div>
</form> </form>
@ -94,3 +108,101 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block customJS %}
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize Lucide icons
lucide.createIcons();
// Add form styling to inputs
const formInputs = document.querySelectorAll('input, select, textarea');
formInputs.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', 'text-gray-900');
});
// Form validation
const form = document.getElementById('meetingForm');
if (form) {
form.addEventListener('submit', function(e) {
const submitBtn = form.querySelector('button[type="submit"]');
const topic = document.getElementById('id_topic');
const startTime = document.getElementById('id_start_time');
const duration = document.getElementById('id_duration');
// Add loading state
submitBtn.disabled = true;
submitBtn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 mr-2 animate-spin"></i> {% trans "Scheduling..." %}`;
// Basic validation
if (startTime && !startTime.value) {
e.preventDefault();
submitBtn.disabled = false;
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Schedule Meeting" %}`;
alert('{% trans "Please select a start time." %}');
return;
}
if (duration && !duration.value) {
e.preventDefault();
submitBtn.disabled = false;
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Schedule Meeting" %}`;
alert('{% trans "Please specify the duration." %}');
return;
}
// Validate that start time is in the future
if (startTime && startTime.value) {
const selectedDate = new Date(startTime.value);
const now = new Date();
if (selectedDate <= now) {
e.preventDefault();
submitBtn.disabled = false;
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Schedule Meeting" %}`;
alert('{% trans "Please select a future date and time for the interview." %}');
return;
}
}
});
}
// Warn before leaving if form has data
let formChanged = false;
const inputsToTrack = form ? form.querySelectorAll('input, select, textarea') : [];
inputsToTrack.forEach(input => {
input.addEventListener('change', function() {
formChanged = true;
});
});
window.addEventListener('beforeunload', function(e) {
if (formChanged) {
e.preventDefault();
e.returnValue = '{% trans "You have unsaved changes. Are you sure you want to leave?" %}';
return e.returnValue;
}
});
if (form) {
form.addEventListener('submit', function() {
formChanged = false;
});
}
// Set minimum date to today for datetime-local input
const startTimeInput = document.getElementById('id_start_time');
if (startTimeInput) {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const minDateTime = `${year}-${month}-${day}T${hours}:${minutes}`;
startTimeInput.setAttribute('min', minDateTime);
}
});
</script>
{% endblock %}

View File

@ -2,432 +2,555 @@
{% load static i18n %} {% load static i18n %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% block title %}{{ title }} - {{ block.super }}{% endblock %} {% block title %}{{ title }}{% endblock %}
{% block customCSS %} {% block customCSS %}
<style> <style>
/* UI Variables for the KAAT-S Theme */ /* Card Hover Effects */
:root { .form-card {
--kaauh-teal: #00636e; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--kaauh-teal-dark: #004a53;
--kaauh-border: #eaeff3;
--kaauh-gray-light: #f8f9fa;
} }
/* Form Container Styling */ .form-card:hover {
.form-container { transform: translateY(-2px);
max-width: 800px; box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
margin: 0 auto;
} }
/* Card Styling */ /* Button Hover Effects */
.card { .btn-action {
border: 1px solid var(--kaauh-border);
border-radius: 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
}
/* Main Action Button Style */
.btn-main-action {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
transition: all 0.2s ease; transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 1.5rem;
} }
.btn-main-action:hover { .btn-action:hover {
background-color: var(--kaauh-teal-dark); transform: translateY(-1px);
border-color: var(--kaauh-teal-dark); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
} }
/* Secondary Button Style */ .btn-primary {
.btn-outline-secondary { transition: all 0.2s ease;
color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal);
}
.btn-outline-secondary:hover {
background-color: var(--kaauh-teal-dark);
color: white;
border-color: var(--kaauh-teal-dark);
} }
/* Form Field Styling */ .btn-primary:hover {
.form-control:focus { transform: translateY(-1px);
border-color: var(--kaauh-teal); box-shadow: 0 4px 12px rgba(157, 34, 53, 0.4);
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25); }
.btn-secondary {
transition: all 0.2s ease;
}
.btn-secondary:hover {
background-color: rgba(157, 34, 53, 0.05);
border-color: rgba(157, 34, 53, 0.3);
}
.btn-danger {
transition: all 0.2s ease;
}
.btn-danger:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
}
/* Input Focus Animation */
.form-input {
transition: all 0.2s ease;
}
.form-input:focus {
transform: translateY(-1px);
}
/* Custom Form Fields */
.form-field {
transition: all 0.2s ease;
}
.form-field:focus-within {
transform: translateY(-1px);
}
.form-label {
color: #374151;
font-weight: 600;
font-size: 0.875rem;
display: block;
margin-bottom: 0.5rem;
}
.form-input {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #e5e7eb;
border-radius: 0.75rem;
background-color: #ffffff;
color: #111827;
font-size: 0.95rem;
transition: all 0.2s ease;
outline: none;
}
.form-input:focus {
border-color: #9d2235;
box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
}
.form-input::placeholder {
color: #9ca3af;
}
.form-input:disabled {
background-color: #f9fafb;
color: #9ca3af;
cursor: not-allowed;
}
.form-select {
width: 100%;
padding: 0.75rem 1rem;
border: 2px solid #e5e7eb;
border-radius: 0.75rem;
background-color: #ffffff;
color: #111827;
font-size: 0.95rem;
transition: all 0.2s ease;
outline: none;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.75rem center;
background-repeat: no-repeat;
background-size: 1.5rem 1.5rem;
padding-right: 2.5rem;
} }
.form-select:focus { .form-select:focus {
border-color: var(--kaauh-teal); border-color: #9d2235;
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25); box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
} }
/* Breadcrumb Styling */ .form-select:disabled {
.breadcrumb { background-color: #f9fafb;
background-color: transparent; color: #9ca3af;
padding: 0; cursor: not-allowed;
margin-bottom: 1rem;
} }
.breadcrumb-item + .breadcrumb-item::before { .form-textarea {
content: ">"; width: 100%;
color: var(--kaauh-teal); padding: 0.75rem 1rem;
border: 2px solid #e5e7eb;
border-radius: 0.75rem;
background-color: #ffffff;
color: #111827;
font-size: 0.95rem;
transition: all 0.2s ease;
outline: none;
resize: vertical;
min-height: 100px;
} }
/* Alert Styling */ .form-textarea:focus {
.alert { border-color: #9d2235;
border-radius: 0.5rem; box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
border: none;
} }
/* Loading State */ .form-helptext {
.btn.loading { margin-top: 0.375rem;
position: relative; font-size: 0.8rem;
pointer-events: none; color: #6b7280;
opacity: 0.8;
} }
.btn.loading::after { .form-error {
content: ""; margin-top: 0.375rem;
position: absolute; font-size: 0.8rem;
width: 16px; color: #dc2626;
height: 16px; font-weight: 500;
margin: auto;
border: 2px solid transparent;
border-top-color: #ffffff;
border-radius: 50%;
animation: spin 1s linear infinite;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Current Profile Section */
.current-profile {
background-color: var(--kaauh-gray-light);
border-radius: 0.5rem;
padding: 1rem;
margin-bottom: 1rem;
}
.current-profile h6 {
color: var(--kaauh-teal-dark);
font-weight: 600;
margin-bottom: 0.75rem;
}
.current-image {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 50%;
border: 2px solid var(--kaauh-teal);
margin-right: 1rem;
} }
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="max-w-4xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<div class="form-container">
<!-- Breadcrumb Navigation --> <!-- Breadcrumb -->
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb" class="mb-6">
<ol class="breadcrumb"> <ol class="flex items-center gap-2 text-sm">
<li class="breadcrumb-item"> <li>
<a href="{% url 'source_list' %}" class="text-decoration-none text-secondary"> <a href="{% url 'source_list' %}"
<i class="fas fa-plug me-1"></i> {% trans "Sources" %} 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 "Sources" %}
</a> </a>
</li> </li>
{% if source %} {% if source %}
<li class="breadcrumb-item"> <li class="text-gray-400">/</li>
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none text-secondary"> <li>
<a href="{% url 'source_detail' source.pk %}"
class="text-gray-500 hover:text-temple-red transition-colors">
{{ source.name }} {{ source.name }}
</a> </a>
</li> </li>
<li class="breadcrumb-item active" aria-current="page" <li class="text-gray-400">/</li>
style=" <li class="text-temple-red font-semibold">{% trans "Update" %}</li>
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;">{% trans "Update" %}</li>
{% else %} {% else %}
<li class="breadcrumb-item active" aria-current="page" <li class="text-gray-400">/</li>
style=" <li class="text-temple-red font-semibold">{% trans "Create" %}</li>
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;">{% trans "Create" %}</li>
{% endif %} {% endif %}
</ol> </ol>
</nav> </nav>
<!-- Header --> <!-- 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">
<h4 style="color: var(--kaauh-teal-dark); font-weight: 700;">
<i class="fas fa-plug me-2"></i> {{ title }}
</h4>
<div class="d-flex gap-2">
{% if source %}
<a href="{% url 'source_detail' source.pk %}" class="btn btn-outline-secondary">
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
</a>
<a href="{% url 'source_delete' source.pk %}" class="btn btn-danger">
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
</a>
{% endif %}
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
</a>
</div>
</div>
{% if source %}
<!-- Current Source Info -->
<div class="card shadow-sm mb-4">
<div class="card-body">
<div class="current-profile">
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
<div class="d-flex align-items-center">
<div class="current-image d-flex align-items-center justify-content-center bg-light">
<i class="fas fa-plug text-muted"></i>
</div>
<div> <div>
<h5 class="mb-1">{{ source.name }}</h5> <h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
{% if source.source_type %} <div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
<p class="text-muted mb-0">{% trans "Type" %}: {{ source.get_source_type_display }}</p> {% if source %}
<i data-lucide="edit-3" class="w-6 h-6 text-temple-red"></i>
{% else %}
<i data-lucide="plus-circle" class="w-6 h-6 text-temple-red"></i>
{% endif %} {% endif %}
{% if source.ip_address %} </div>
<p class="text-muted mb-0">{% trans "IP Address" %}: {{ source.ip_address }}</p> {{ title }}
</h1>
<p class="text-gray-500 text-sm sm:text-base">
{% if source %}
{% trans "Update integration source details" %}
{% else %}
{% trans "Create a new integration source" %}
{% endif %} {% endif %}
<small class="text-muted"> </p>
{% trans "Created" %}: {{ source.created_at|date:"d M Y" }} •
{% trans "Last Updated" %}: {{ source.updated_at|date:"d M Y" }}
</small>
</div>
</div>
</div>
</div>
</div> </div>
<div class="flex gap-3">
{% if source %}
<a href="{% url 'source_detail' source.pk %}"
class="btn-secondary 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">
<i data-lucide="eye" class="w-5 h-5"></i>
{% trans "View Details" %}
</a>
<a href="{% url 'source_delete' source.pk %}"
class="btn-danger inline-flex items-center gap-2 px-5 py-3 bg-red-500 text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
<i data-lucide="trash-2" class="w-5 h-5"></i>
{% trans "Delete" %}
</a>
{% endif %} {% endif %}
<a href="{% url 'source_list' %}"
class="btn-secondary 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">
<i data-lucide="arrow-left" class="w-5 h-5"></i>
{% trans "Back to List" %}
</a>
</div>
</div>
<!-- Form Card --> <!-- Form Card -->
<div class="card shadow-sm"> <div class="form-card bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div class="card-body p-4"> <!-- Card Header -->
<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="database" class="w-5 h-5 text-temple-red"></i>
</div>
<div>
<h2 class="text-lg font-bold text-gray-900">{% trans "Source Configuration" %}</h2>
<p class="text-sm text-gray-500">{% trans "Fill in the integration details below" %}</p>
</div>
</div>
</div>
<!-- Card Body -->
<div class="p-6 sm:p-8">
<!-- Non-Field Errors -->
{% if form.non_field_errors %} {% if form.non_field_errors %}
<div class="alert alert-danger" role="alert"> <div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl">
<h5 class="alert-heading"> <div class="flex items-start gap-3">
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %} <div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center flex-shrink-0">
</h5> <i data-lucide="alert-circle" class="w-5 h-5 text-red-500"></i>
</div>
<div class="flex-1">
<h3 class="text-base font-bold text-red-700 mb-1">{% trans "Error" %}</h3>
{% for error in form.non_field_errors %} {% for error in form.non_field_errors %}
<p class="mb-0">{{ error }}</p> <p class="text-sm text-red-600">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
</div>
</div>
{% endif %} {% endif %}
<!-- Messages -->
{% if messages %} {% if messages %}
<div class="space-y-3 mb-6">
{% for message in messages %} {% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert"> <div class="flex items-start gap-3 p-4 rounded-xl border
{{ message }} {% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-700
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button> {% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-700
{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-700
{% else %}bg-blue-50 border-blue-200 text-blue-700{% endif %}">
<div class="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0
{% if message.tags == 'error' %}bg-red-100
{% elif message.tags == 'success' %}bg-green-100
{% elif message.tags == 'warning' %}bg-yellow-100
{% else %}bg-blue-100{% endif %}">
<i data-lucide="{% if message.tags == 'error' %}alert-circle
{% elif message.tags == 'success' %}check-circle
{% elif message.tags == 'warning' %}alert-triangle
{% else %}info{% endif %}"
class="w-5 h-5
{% if message.tags == 'error' %}text-red-500
{% elif message.tags == 'success' %}text-green-500
{% elif message.tags == 'warning' %}text-yellow-600
{% else %}text-blue-500{% endif %}">
</i>
</div>
<div class="flex-1">
<p class="text-sm font-medium">{{ message }}</p>
</div>
<button type="button"
onclick="this.parentElement.remove()"
class="text-current hover:opacity-70 transition-opacity">
<i data-lucide="x" class="w-4 h-4"></i>
</button>
</div> </div>
{% endfor %} {% endfor %}
</div>
{% endif %} {% endif %}
<form method="post" novalidate id="source-form"> <!-- Form -->
<form method="post" novalidate id="source-form" class="space-y-6">
{% csrf_token %} {% csrf_token %}
<div class="row"> <!-- Name Field -->
<div class="col-md-6"> <div class="form-field">
<div class="mb-3"> <label for="{{ form.name.id_for_label }}" class="form-label flex items-center gap-2">
<label for="{{ form.name.id_for_label }}" class="form-label"> <i data-lucide="tag" class="w-4 h-4 text-gray-400"></i>
{{ form.name.label }} <span class="text-danger">*</span> {% trans "Name" %}
{% if form.name.field.required %}<span class="text-temple-red">*</span>{% endif %}
</label> </label>
{{ form.name|add_class:"form-control" }} <input type="{{ form.name.field.widget.input_type }}"
{% if form.name.errors %} name="{{ form.name.name }}"
<div class="invalid-feedback d-block"> id="{{ form.name.id_for_label }}"
class="form-input"
placeholder="{% trans 'Enter a name for this integration source' %}"
{% if form.name.value %}value="{{ form.name.value }}"{% endif %}>
{% if form.name.help_text %}
<p class="form-helptext">{{ form.name.help_text }}</p>
{% endif %}
{% for error in form.name.errors %} {% for error in form.name.errors %}
{{ error }} <p class="form-error">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
<div class="form-text">{{ form.name.help_text }}</div> <!-- Source Type Field -->
</div> <div class="form-field">
</div> <label for="{{ form.source_type.id_for_label }}" class="form-label flex items-center gap-2">
<div class="col-md-6"> <i data-lucide="server" class="w-4 h-4 text-gray-400"></i>
<div class="mb-3"> {% trans "Source Type" %}
<label for="{{ form.source_type.id_for_label }}" class="form-label"> {% if form.source_type.field.required %}<span class="text-temple-red">*</span>{% endif %}
{{ form.source_type.label }} <span class="text-danger">*</span>
</label> </label>
{{ form.source_type|add_class:"form-control" }} {{ form.source_type.as_widget }}
{% if form.source_type.errors %} {% if form.source_type.help_text %}
<div class="invalid-feedback d-block"> <p class="form-helptext">{{ form.source_type.help_text }}</p>
{% endif %}
{% for error in form.source_type.errors %} {% for error in form.source_type.errors %}
{{ error }} <p class="form-error">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
<div class="form-text">{{ form.source_type.help_text }}</div>
</div>
</div>
</div>
<div class="row"> <!-- IP Address Field -->
<div class="col-md-6"> <div class="form-field">
<div class="mb-3"> <label for="{{ form.ip_address.id_for_label }}" class="form-label flex items-center gap-2">
<label for="{{ form.ip_address.id_for_label }}" class="form-label"> <i data-lucide="globe" class="w-4 h-4 text-gray-400"></i>
{{ form.ip_address.label }} <span class="text-danger">*</span> {% trans "IP Address" %}
{% if form.ip_address.field.required %}<span class="text-temple-red">*</span>{% endif %}
</label> </label>
{{ form.ip_address|add_class:"form-control" }} <input type="{{ form.ip_address.field.widget.input_type }}"
{% if form.ip_address.errors %} name="{{ form.ip_address.name }}"
<div class="invalid-feedback d-block"> id="{{ form.ip_address.id_for_label }}"
class="form-input"
placeholder="{% trans 'e.g., 192.168.1.100' %}"
{% if form.ip_address.value %}value="{{ form.ip_address.value }}"{% endif %}>
{% if form.ip_address.help_text %}
<p class="form-helptext">{{ form.ip_address.help_text }}</p>
{% endif %}
{% for error in form.ip_address.errors %} {% for error in form.ip_address.errors %}
{{ error }} <p class="form-error">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
<div class="form-text">{{ form.ip_address.help_text }}</div> <!-- Trusted IPs Field -->
</div> <div class="form-field">
</div> <label for="{{ form.trusted_ips.id_for_label }}" class="form-label flex items-center gap-2">
<div class="col-md-6"> <i data-lucide="shield" class="w-4 h-4 text-gray-400"></i>
<div class="mb-3"> {% trans "Trusted IPs" %}
<label for="{{ form.trusted_ips.id_for_label }}" class="form-label">
{{ form.trusted_ips.label }}
</label> </label>
{{ form.trusted_ips|add_class:"form-control" }} <input type="{{ form.trusted_ips.field.widget.input_type }}"
{% if form.trusted_ips.errors %} name="{{ form.trusted_ips.name }}"
<div class="invalid-feedback d-block"> id="{{ form.trusted_ips.id_for_label }}"
class="form-input"
placeholder="{% trans 'e.g., 192.168.1.1, 192.168.1.2 (comma-separated)' %}"
{% if form.trusted_ips.value %}value="{{ form.trusted_ips.value }}"{% endif %}>
{% if form.trusted_ips.help_text %}
<p class="form-helptext">{{ form.trusted_ips.help_text }}</p>
{% endif %}
{% for error in form.trusted_ips.errors %} {% for error in form.trusted_ips.errors %}
{{ error }} <p class="form-error">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
<div class="form-text">{{ form.trusted_ips.help_text }}</div>
</div>
</div>
</div>
<div class="mb-3"> <!-- Description Field -->
<label for="{{ form.description.id_for_label }}" class="form-label"> <div class="form-field">
{{ form.description.label }} <label for="{{ form.description.id_for_label }}" class="form-label flex items-center gap-2">
<i data-lucide="align-left" class="w-4 h-4 text-gray-400"></i>
{% trans "Description" %}
</label> </label>
{{ form.description|add_class:"form-control" }} <textarea name="{{ form.description.name }}"
{% if form.description.errors %} id="{{ form.description.id_for_label }}"
<div class="invalid-feedback d-block"> class="form-textarea"
placeholder="{% trans 'Add a description...' %}">{% if form.description.value %}{{ form.description.value }}{% endif %}</textarea>
{% if form.description.help_text %}
<p class="form-helptext">{{ form.description.help_text }}</p>
{% endif %}
{% for error in form.description.errors %} {% for error in form.description.errors %}
{{ error }} <p class="form-error">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
<div class="form-text">{{ form.description.help_text }}</div>
</div>
<div class="row"> <!-- Is Active Field -->
<div class="col-md-6"> <div class="form-field">
<div class="mb-3"> <label for="{{ form.is_active.id_for_label }}" class="form-label flex items-center gap-2">
<div class="form-check"> <i data-lucide="power" class="w-4 h-4 text-gray-400"></i>
{{ form.is_active|add_class:"form-check-input" }} {% trans "Active Status" %}
<label for="{{ form.is_active.id_for_label }}" class="form-check-label">
{{ form.is_active.label }}
</label> </label>
<div class="flex items-center gap-3">
{{ form.is_active.as_widget }}
<span class="text-sm text-gray-600">{% trans "Enable this integration source" %}</span>
</div> </div>
{% if form.is_active.errors %} {% if form.is_active.help_text %}
<div class="invalid-feedback d-block"> <p class="form-helptext">{{ form.is_active.help_text }}</p>
{% endif %}
{% for error in form.is_active.errors %} {% for error in form.is_active.errors %}
{{ error }} <p class="form-error">{{ error }}</p>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
<div class="form-text">{{ form.is_active.help_text }}</div>
</div>
</div>
</div>
<!-- API Credentials Section --> <!-- API Credentials Section (for existing sources) -->
{% if source %} {% if source %}
<div class="card bg-light mb-4"> <div class="mt-8 p-6 bg-gray-50 rounded-2xl border border-gray-200">
<div class="card-header"> <div class="flex items-center gap-3 mb-4">
<h6 class="mb-0">{% trans "API Credentials" %}</h6> <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>
<div class="card-body"> <div>
<div class="row"> <h3 class="text-lg font-bold text-gray-900">{% trans "API Credentials" %}</h3>
<div class="col-md-6"> <p class="text-sm text-gray-500">{% trans "Generated credentials for API access" %}</p>
<div class="mb-3"> </div>
<label class="form-label">{% trans "API Key" %}</label> </div>
<div class="input-group"> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<input type="text" class="form-control" value="{{ source.api_key }}" readonly> <div>
<button type="button" class="btn btn-outline-secondary" <label class="block 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="form-input rounded-r-none">
<button type="button"
hx-post="{% url 'copy_to_clipboard' %}" hx-post="{% url 'copy_to_clipboard' %}"
hx-vals='{"text": "{{ source.api_key }}"}' hx-vals='{"text": "{{ source.api_key }}"}'
title="{% trans 'Copy to clipboard' %}"> title="{% trans 'Copy to clipboard' %}"
<i class="fas fa-copy"></i> class="btn-action inline-flex items-center justify-center px-3 border border-l-0 border-gray-300 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-r-lg transition">
<i data-lucide="copy" class="w-4 h-4"></i>
</button> </button>
</div> </div>
</div> </div>
</div> <div>
<div class="col-md-6"> <label class="block text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
<div class="mb-3"> <i data-lucide="lock" class="w-4 h-4 text-gray-400"></i>
<label class="form-label">{% trans "API Secret" %}</label> {% trans "API Secret" %}
<div class="input-group"> </label>
<input type="password" class="form-control" value="{{ source.api_secret }}" readonly id="api-secret"> <div class="flex">
<button type="button" class="btn btn-outline-secondary" onclick="toggleSecretVisibility()"> <input type="password" value="{{ source.api_secret }}" readonly id="api-secret" class="form-input rounded-l-lg">
<i class="fas fa-eye" id="secret-toggle-icon"></i> <button type="button" onclick="toggleSecretVisibility()" class="btn-action inline-flex items-center justify-center px-3 border border-l-0 border-r-0 border-gray-300 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>
<button type="button" class="btn btn-outline-secondary" <button type="button"
hx-post="{% url 'copy_to_clipboard' %}" hx-post="{% url 'copy_to_clipboard' %}"
hx-vals='{"text": "{{ source.api_secret }}"}' hx-vals='{"text": "{{ source.api_secret }}"}'
title="{% trans 'Copy to clipboard' %}"> title="{% trans 'Copy to clipboard' %}"
<i class="fas fa-copy"></i> class="btn-action inline-flex items-center justify-center px-3 border border-l-0 border-gray-300 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-r-lg transition">
<i data-lucide="copy" class="w-4 h-4"></i>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="mt-4">
<div class="text-end"> <a href="{% url 'generate_api_keys' source.pk %}"
<a href="{% url 'generate_api_keys' source.pk %}" class="btn btn-warning"> class="btn-action inline-flex items-center gap-2 px-5 py-3 bg-yellow-500 text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
<i class="fas fa-key"></i> {% trans "Generate New Keys" %} <i data-lucide="refresh-cw" class="w-5 h-5"></i>
{% trans "Generate New Keys" %}
</a> </a>
</div> </div>
</div> </div>
</div>
{% endif %} {% endif %}
<div class="d-flex gap-2"> <!-- Form Actions -->
<button form="source-form" type="submit" class="btn btn-main-action"> <div class="flex flex-col sm:flex-row gap-3 pt-6 border-t border-gray-200 mt-6">
<i class="fas fa-save me-1"></i> {% trans "Save" %} <button form="source-form" type="submit"
class="btn-primary flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py-3 bg-temple-red text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
<i data-lucide="save" class="w-5 h-5"></i>
{% trans "Save" %}
</button> </button>
<a href="{% url 'source_list' %}"
class="btn-secondary flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300">
<i data-lucide="x" class="w-5 h-5"></i>
{% trans "Cancel" %}
</a>
</div> </div>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}
{% block extra_js %} {% block customJS %}
<script> <script>
// Initialize Lucide icons
lucide.createIcons();
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
// Auto-adjust textarea height
const textarea = document.querySelector('textarea[name="description"]');
if (textarea) {
textarea.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = this.scrollHeight + 'px';
});
// Set initial height
textarea.style.height = textarea.scrollHeight + 'px';
}
// Apply Tailwind classes to source_type select
const sourceTypeSelect = document.querySelector('select[name="source_type"]');
if (sourceTypeSelect) {
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');
if (form) { if (form) {
form.addEventListener('submit', function(e) { form.addEventListener('submit', function(e) {
const submitBtn = form.querySelector('button[type="submit"]'); const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.classList.add('loading');
submitBtn.disabled = true;
// Basic validation // Basic validation
const name = document.getElementById('id_name'); const name = document.getElementById('id_name');
if (name && !name.value.trim()) { if (name && !name.value.trim()) {
e.preventDefault(); e.preventDefault();
submitBtn.classList.remove('loading');
submitBtn.disabled = false;
alert('{% trans "Source name is required." %}'); alert('{% trans "Source name is required." %}');
return; return;
} }
@ -435,20 +558,12 @@ document.addEventListener('DOMContentLoaded', function() {
const ipAddress = document.getElementById('id_ip_address'); const ipAddress = document.getElementById('id_ip_address');
if (ipAddress && ipAddress.value.trim() && !isValidIP(ipAddress.value.trim())) { if (ipAddress && ipAddress.value.trim() && !isValidIP(ipAddress.value.trim())) {
e.preventDefault(); e.preventDefault();
submitBtn.classList.remove('loading');
submitBtn.disabled = false;
alert('{% trans "Please enter a valid IP address." %}'); alert('{% trans "Please enter a valid IP address." %}');
return; return;
} }
}); });
} }
// IP validation helper
function isValidIP(ip) {
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return ipRegex.test(ip);
}
// Warn before leaving if changes are made // Warn before leaving if changes are made
let formChanged = false; let formChanged = false;
const formInputs = form ? form.querySelectorAll('input, select, textarea') : []; const formInputs = form ? form.querySelectorAll('input, select, textarea') : [];
@ -474,34 +589,42 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
// Toggle Secret Visibility
function toggleSecretVisibility() { 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();
}
// IP validation helper
function isValidIP(ip) {
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
return ipRegex.test(ip);
} }
// Handle HTMX copy to clipboard feedback // Handle HTMX copy to clipboard feedback
document.body.addEventListener('htmx:afterRequest', function(evt) { 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');
button.classList.add('btn-success'); button.classList.add('bg-green-500', 'text-white');
lucide.createIcons();
setTimeout(() => { setTimeout(() => {
button.innerHTML = originalIcon; button.innerHTML = originalHTML;
button.classList.remove('btn-success'); button.classList.remove('bg-green-500', 'text-white');
button.classList.add('btn-outline-secondary'); button.classList.add('bg-gray-100', 'hover:bg-gray-200', 'text-gray-600');
lucide.createIcons();
}, 2000); }, 2000);
} }
}); });

View File

@ -1,166 +1,269 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static i18n %} {% load static i18n %}
{% block title %}{% trans "Sources" %}{% endblock %} {% block title %}{% trans "Sources" %} - University ATS{% endblock %}
{% block content %} {% block content %}
<div class="space-y-6">
<div class="container-fluid"> <!-- Mobile Header -->
<nav aria-label="breadcrumb"> <div class="lg:hidden mb-4">
<ol class="breadcrumb"> <div class="flex items-center justify-between mb-4">
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li> <h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
<li class="breadcrumb-item active" aria-current="page" style=" <div class="bg-temple-red/10 p-2 rounded-lg">
color: #F43B5E; /* Rosy Accent Color */ <i data-lucide="database" class="w-6 h-6 text-temple-red"></i>
font-weight: 600; </div>
">{% trans "Sources Settings" %}</li> {% trans "Integration Sources" %}
</ol> </h1>
</nav> <a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl text-sm transition shadow-sm">
<div class="row"> <i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add Source" %}
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 mb-0">{% trans "Integration Sources" %}</h1>
<a href="{% url 'source_create' %}" class="btn btn-main-action">
{% trans "Create Source for Integration" %} <i class="fas fa-plus"></i>
</a> </a>
</div> </div>
<!-- Search and Filters --> <!-- Mobile Filters -->
<div class="card mb-4"> <div class="space-y-3">
<div class="card-body"> <form method="get" class="flex gap-2">
<form method="get" class="row g-3"> <div class="flex-1">
<div class="col-md-8"> <div class="relative">
<div class="input-group"> <i data-lucide="search" class="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2"></i>
<span class="input-group-text"> <input type="text" name="q"
<i class="fas fa-search"></i> placeholder="{% trans 'Search sources...' %}"
</span> value="{{ search_query }}"
<input type="text" class="form-control" name="q" class="w-full pl-10 pr-4 py-2.5 bg-white border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
placeholder="Search sources..." value="{{ search_query }}">
</div> </div>
</div> </div>
<div class="col-md-4"> <button type="submit" class="bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2.5 rounded-xl transition">
<button type="submit" class="btn btn-outline-primary"> <i data-lucide="search" class="w-4 h-4"></i>
<i class="fas fa-search"></i> {% trans "Search" %}
</button> </button>
{% if search_query %} {% if search_query %}
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary"> <a href="{% url 'source_list' %}" class="bg-gray-100 hover:bg-gray-200 text-gray-600 px-4 py-2.5 rounded-xl transition">
<i class="fas fa-times"></i> {% trans "Clear" %} <i data-lucide="x" class="w-4 h-4"></i>
</a> </a>
{% endif %} {% endif %}
</div>
</form> </form>
</div> </div>
</div> </div>
<!-- Desktop Header -->
<div class="hidden lg:block">
<!-- Breadcrumb -->
<nav class="mb-6" aria-label="breadcrumb">
<ol class="flex items-center gap-2 text-sm flex-wrap">
<li><a href="{% url 'settings' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
<i data-lucide="settings" class="w-4 h-4"></i> {% trans "Settings" %}
</a></li>
<li class="text-gray-400">/</li>
<li class="text-temple-red font-semibold">{% trans "Sources Settings" %}</li>
</ol>
</nav>
<div class="flex justify-between items-start mb-6">
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
<div class="bg-temple-red/10 p-3 rounded-xl">
<i data-lucide="database" class="w-8 h-8 text-temple-red"></i>
</div>
{% trans "Integration Sources" %}
</h1>
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create New Source" %}
</a>
</div>
<!-- Desktop Filters -->
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<h5 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="filter" class="w-5 h-5"></i>
{% trans "Search Sources" %}
</h5>
</div>
<div class="p-6">
<form method="get" class="flex gap-3">
<div class="flex-1">
<div class="relative">
<i data-lucide="search" class="w-5 h-5 text-gray-400 absolute left-4 top-1/2 -translate-y-1/2"></i>
<input type="text" name="q"
placeholder="{% trans 'Search by name or type...' %}"
value="{{ search_query }}"
class="w-full pl-11 pr-4 py-2.5 bg-white border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
</div>
</div>
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-5 py-2.5 rounded-xl text-sm transition">
<i data-lucide="search" class="w-4 h-4"></i> {% trans "Search" %}
</button>
{% if search_query %}
<a href="{% url 'source_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="x" class="w-4 h-4"></i> {% trans "Clear" %}
</a>
{% endif %}
</form>
</div>
</div>
</div>
<!-- Results Summary --> <!-- Results Summary -->
{% if search_query %} {% if search_query %}
<div class="alert alert-info"> <div class="bg-blue-50 border border-blue-200 rounded-xl p-4 flex items-center gap-3">
Found {{ total_sources }} source{{ total_sources|pluralize }} matching "{{ search_query }}" <i data-lucide="info" class="w-5 h-5 text-blue-600"></i>
<p class="text-sm text-blue-800">
{% blocktrans %}Found {{ count }} source{{ count|pluralize }} matching "{{ search_query }}"{% endblocktrans %}
</p>
</div> </div>
{% endif %} {% endif %}
<!-- Sources Table --> {# --- MOBILE CARD VIEW --- #}
<div class="card"> <div class="lg:hidden grid grid-cols-1 gap-4">
<div class="card-body"> {% for source in page_obj %}
{% if page_obj %} <div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="table-responsive"> <div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<table class="table table-hover"> <div class="flex justify-between items-start">
<thead class="table-light"> <h5 class="font-bold text-lg">
<a href="{% url 'source_detail' source.pk %}" class="hover:text-white/80 transition-colors">{{ source.name }}</a>
</h5>
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2.5 py-1 rounded-full bg-white/20 backdrop-blur-sm border border-white/30">
{{ source.source_type }}
</span>
</div>
<p class="text-xs text-white/80 mt-1">ID: {{ source.pk }}</p>
</div>
<div class="p-4 space-y-3">
<div class="flex items-center gap-2 text-sm text-gray-600">
<i data-lucide="key" class="w-4 h-4 text-temple-red"></i>
<code class="text-xs bg-gray-100 px-2 py-1 rounded">{{ source.api_key|truncatechars:20 }}</code>
</div>
<div class="flex items-center gap-2 text-sm text-gray-600">
<i data-lucide="calendar" class="w-4 h-4 text-temple-red"></i>
<span>{% trans "Created" %}: {{ source.created_at|date:"M d, Y" }}</span>
</div>
<div class="flex items-center gap-2 text-sm">
<i data-lucide="activity" class="w-4 h-4 text-temple-red"></i>
{% if source.is_active %}
<span class="text-emerald-600 font-semibold">{% trans "Active" %}</span>
{% else %}
<span class="text-gray-500 font-semibold">{% trans "Inactive" %}</span>
{% endif %}
</div>
<!-- Actions -->
<div class="flex gap-2 pt-3 border-t border-gray-100">
<a href="{% url 'source_detail' source.pk %}" class="flex-1 inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Details" %}
</a>
<a href="{% url 'source_update' source.pk %}" class="inline-flex items-center justify-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-50 px-4 py-2.5 rounded-xl text-sm transition">
<i data-lucide="edit-2" class="w-4 h-4"></i>
</a>
</div>
</div>
</div>
{% empty %}
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden text-center p-8">
<i data-lucide="database" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No sources found" %}</h3>
<p class="text-gray-500 mb-4">
{% if search_query %}
{% blocktrans %}No sources match your search criteria "{{ search_query }}".{% endblocktrans %}
{% else %}
{% trans "Get started by creating your first source." %}
{% endif %}
</p>
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-5 py-2.5 rounded-xl transition shadow-sm">
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create Source" %}
</a>
</div>
{% endfor %}
</div>
{# --- DESKTOP TABLE VIEW --- #}
<div class="hidden lg:block bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
<h5 class="text-lg font-bold flex items-center gap-2">
<i data-lucide="list" class="w-5 h-5"></i>
{% trans "Source Listings" %}
</h5>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr> <tr>
<th>{% trans "Name" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Name" %}</th>
<th>{% trans "Type" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Type" %}</th>
<th>{% trans "Status" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Status" %}</th>
<th>{% trans "API Key" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "API Key" %}</th>
<th>{% trans "Created" %}</th> <th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Created" %}</th>
<th>{% trans "Actions" %}</th> <th scope="col" class="px-6 py-3 text-center text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody class="bg-white divide-y divide-gray-100">
{% for source in page_obj %} {% for source in page_obj %}
<tr> <tr class="hover:bg-gray-50 transition-colors">
<td> <td class="px-6 py-4">
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none text-primary-theme"> <a href="{% url 'source_detail' source.pk %}" class="text-temple-red font-semibold hover:text-[#7a1a29] transition-colors">{{ source.name }}</a>
<strong>{{ source.name }}</strong> <br>
</a> <span class="text-xs text-gray-500">ID: {{ source.pk }}</span>
</td> </td>
<td> <td class="px-6 py-4">
<span class="badge bg-primary-theme">{{ source.source_type }}</span> <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.source_type }}
</span>
</td> </td>
<td> <td class="px-6 py-4">
{% if source.is_active %} {% if source.is_active %}
<span class="badge bg-primary-theme">{% trans "Active" %}</span> <span class="inline-flex items-center gap-1.5 text-sm font-semibold text-emerald-600">
<i data-lucide="check-circle-2" class="w-4 h-4"></i> {% trans "Active" %}
</span>
{% else %} {% else %}
<span class="badge bg-primary-theme">{% trans "Inactive" %}</span> <span class="inline-flex items-center gap-1.5 text-sm font-semibold text-gray-500">
<i data-lucide="x-circle" class="w-4 h-4"></i> {% trans "Inactive" %}
</span>
{% endif %} {% endif %}
</td> </td>
<td> <td class="px-6 py-4">
<code class="small text-primary-theme">{{ source.api_key|truncatechars:20 }}</code> <code class="text-xs bg-gray-100 px-2 py-1 rounded text-temple-red">{{ source.api_key|truncatechars:20 }}</code>
</td> </td>
<td> <td class="px-6 py-4 text-sm text-gray-700">{{ source.created_at|date:"M d, Y" }}</td>
<small class="text-muted">{{ source.created_at|date:"M d, Y" }}</small> <td class="px-6 py-4">
</td> <div class="flex items-center gap-2 justify-center">
<td> <a href="{% url 'source_detail' source.pk %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-temple-red hover:bg-[#7a1a29] text-white transition" title="{% trans 'View' %}">
<div class="btn-group" role="group"> <i data-lucide="eye" class="w-4 h-4"></i>
<a href="{% url 'source_detail' source.pk %}"
class="btn btn-sm btn-outline-primary" title="View">
<i class="fas fa-eye"></i>
</a> </a>
<a href="{% url 'source_update' source.pk %}" <a href="{% url 'source_update' source.pk %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-600 transition" title="{% trans 'Edit' %}">
class="btn btn-sm btn-outline-secondary" title="Edit"> <i data-lucide="edit-2" class="w-4 h-4"></i>
<i class="fas fa-edit"></i>
</a> </a>
{% comment %} <button type="button"
class="btn btn-sm btn-outline-warning"
hx-post="{% url 'toggle_source_status' source.pk %}"
hx-confirm="Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?"
title="{{ source.is_active|yesno:'Deactivate,Activate' }}">
<i class="fas fa-{{ source.is_active|yesno:'pause,play' }}"></i>
</button> {% endcomment %}
{% comment %} <a href="{% url 'source_delete' source.pk %}"
class="btn btn-sm btn-outline-danger" title="Delete">
<i class="fas fa-trash"></i>
</a> {% endcomment %}
</div> </div>
</td> </td>
</tr> </tr>
{% empty %}
<tr>
<td colspan="6" class="px-6 py-12 text-center">
<i data-lucide="database" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No sources found" %}</h3>
<p class="text-gray-500 mb-4">
{% if search_query %}
{% blocktrans %}No sources match your search criteria "{{ search_query }}".{% endblocktrans %}
{% else %}
{% trans "Get started by creating your first source." %}
{% endif %}
</p>
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-5 py-2.5 rounded-xl transition shadow-sm">
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create Source" %}
</a>
</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
<!-- Pagination -->
{% include "includes/paginator.html" %} {% include "includes/paginator.html" %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-database fa-3x text-muted mb-3"></i>
<h5 class="text-muted">{% trans "No sources found" %}</h5>
<p class="text-muted">
{% if search_query %}
{% blocktrans with query=query %}No sources match your search criteria "{{ query }}".{% endblocktrans %}
{% else %}
{% trans "Get started by creating your first source." %}
{% endif %}
</p>
<a href="{% url 'source_create' %}" class="btn btn-main-action">
<i class="fas fa-plus"></i> {% trans "Create Source" %}
</a>
</div> </div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block customJS %}
<script> <script>
// Auto-refresh after status toggle lucide.createIcons();
// Auto-refresh after status toggle (for HTMX)
document.body.addEventListener('htmx:afterRequest', function(evt) { document.body.addEventListener('htmx:afterRequest', function(evt) {
if (evt.detail.successful) { if (evt.detail.successful) {
// Reload the page after a short delay to show updated status
setTimeout(() => { setTimeout(() => {
window.location.reload(); window.location.reload();
}, 500); }, 500);