before fixing the icon
This commit is contained in:
parent
1dcb03a1f0
commit
dba9af100a
25
recruitment/templatetags/brand_logo.py
Normal file
25
recruitment/templatetags/brand_logo.py
Normal 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]
|
||||
}
|
||||
142
recruitment/templatetags/logo_tags.py
Normal file
142
recruitment/templatetags/logo_tags.py
Normal 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)
|
||||
@ -25,7 +25,8 @@ urlpatterns = [
|
||||
# Job-specific Views
|
||||
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>/calendar/", views.interview_calendar_view, name="interview_calendar"),
|
||||
path
|
||||
("jobs/<slug:slug>/calendar/", views.interview_calendar_view, name="interview_calendar"),
|
||||
|
||||
# Job Actions & Integrations
|
||||
path("jobs/<slug:slug>/post-to-linkedin/", views.post_to_linkedin, name="post_to_linkedin"),
|
||||
|
||||
310
templates/TEMPLATE_TAILWIND_CONVERSION_SUMMARY.md
Normal file
310
templates/TEMPLATE_TAILWIND_CONVERSION_SUMMARY.md
Normal 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">​</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.)
|
||||
@ -145,4 +145,4 @@
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>h
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4,291 +4,498 @@
|
||||
{% block title %}{% trans "Application" %}-{{ job.title }}{% endblock %}
|
||||
|
||||
{% 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>
|
||||
/* 1. LAYOUT & GRID */
|
||||
.page-container {
|
||||
max-width: 1200px;
|
||||
margin: 2rem auto 5rem;
|
||||
padding-inline: 1.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 350px;
|
||||
gap: 2rem;
|
||||
align-items: start;
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
:root {
|
||||
--red: #9d2235;
|
||||
--red-dark: #7a1a29;
|
||||
--gray-50: #fafafa;
|
||||
--gray-100: #f5f5f5;
|
||||
--gray-200: #e5e5e5;
|
||||
--gray-400: #a3a3a3;
|
||||
--gray-600: #525252;
|
||||
--gray-900: #171717;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.page-container { grid-template-columns: 1fr; }
|
||||
.desktop-sidebar { display: none; }
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 2. STICKY TOP NAV */
|
||||
.job-nav {
|
||||
background-color: var(--temple-red);
|
||||
color: white;
|
||||
padding: 0.75rem 1.5rem;
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, sans-serif;
|
||||
color: var(--gray-900);
|
||||
background: #ffffff;
|
||||
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;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* 3. CARDS & SECTIONS */
|
||||
.content-card {
|
||||
background: white;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
|
||||
overflow: hidden;
|
||||
.container {
|
||||
max-width: 75rem;
|
||||
margin: 0 auto;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: white;
|
||||
}
|
||||
|
||||
.card-body { padding: 1.5rem; }
|
||||
|
||||
/* 4. METADATA GRID */
|
||||
.meta-section {
|
||||
.content-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 1.25rem;
|
||||
background: #f9fafb;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 3rem;
|
||||
padding: 3rem 0;
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--gray-text);
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.meta-item svg {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
color: var(--temple-red);
|
||||
flex-shrink: 0;
|
||||
/* Details Grid */
|
||||
.details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1.5rem;
|
||||
padding: 2rem;
|
||||
background: var(--gray-50);
|
||||
border-radius: 0.75rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
/* 5. PURE CSS ACCORDION */
|
||||
.accordion-item { border-bottom: 1px solid var(--border); }
|
||||
.accordion-toggle { display: none; }
|
||||
|
||||
.accordion-header {
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1.25rem;
|
||||
cursor: pointer;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.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;
|
||||
color: var(--temple-red-dark);
|
||||
transition: background 0.2s;
|
||||
margin-bottom: 1.5rem;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.accordion-header:hover { background: var(--temple-cream); }
|
||||
|
||||
.accordion-content {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.3s ease-out;
|
||||
color: #4b5563;
|
||||
.section-content {
|
||||
font-size: 1rem;
|
||||
line-height: 1.75;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
.accordion-toggle:checked + .accordion-header + .accordion-content {
|
||||
max-height: 2000px; /* Large enough for content */
|
||||
padding: 1rem 1.25rem 2rem;
|
||||
.section-content h1,
|
||||
.section-content h2,
|
||||
.section-content h3,
|
||||
.section-content h4 {
|
||||
font-weight: 600;
|
||||
color: var(--gray-900);
|
||||
margin: 2rem 0 1rem;
|
||||
}
|
||||
|
||||
.accordion-icon {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
.accordion-toggle:checked + .accordion-header .accordion-icon {
|
||||
transform: rotate(180deg);
|
||||
.section-content h2 { font-size: 1.25rem; }
|
||||
.section-content h3 { font-size: 1.125rem; }
|
||||
|
||||
.section-content p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* 6. SIDEBAR & BUTTONS */
|
||||
.sticky-sidebar {
|
||||
.section-content ul,
|
||||
.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;
|
||||
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;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
background: var(--temple-red);
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
width: 100%;
|
||||
border: none;
|
||||
transition: 0.2s;
|
||||
padding: 1rem 1.5rem;
|
||||
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; }
|
||||
.btn-apply:not(:disabled):hover { background: var(--temple-red-dark); }
|
||||
.apply-button:hover {
|
||||
background: var(--red-dark);
|
||||
}
|
||||
|
||||
/* 7. MOBILE FOOTER */
|
||||
.mobile-apply-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
.apply-text {
|
||||
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;
|
||||
border-top: 1px solid var(--border);
|
||||
box-shadow: 0 -4px 6px -1px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
background: var(--gray-50);
|
||||
border-radius: 0.5rem;
|
||||
font-weight: 600;
|
||||
color: var(--gray-600);
|
||||
}
|
||||
|
||||
@media (min-width: 993px) { .mobile-apply-bar { display: none; } }
|
||||
/* 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;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 1rem;
|
||||
background: #ffffff;
|
||||
border-top: 1px solid var(--gray-200);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
||||
<nav class="job-nav">
|
||||
<div style="max-width: 1200px; margin: 0 auto;">
|
||||
{% trans "Job Overview" %}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page-container">
|
||||
<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 %}
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (typeof lucide !== 'undefined') {
|
||||
lucide.createIcons();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -9,270 +9,89 @@
|
||||
<title>{% trans "Careers" %} - {% block title %}{% trans "Application Form" %}{% endblock %}</title>
|
||||
|
||||
<link rel="icon" type="image/png" href="{% static 'image/favicon/favicon-32x32.png'%}">
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--temple-red: #9d2235;
|
||||
--temple-red-dark: #7a1a29;
|
||||
--temple-dark: #1a1a1a;
|
||||
--temple-cream: #f8f7f2;
|
||||
--white: #ffffff;
|
||||
--light-bg: #f8f9fa;
|
||||
--gray-text: #6c757d;
|
||||
--danger: #dc3545;
|
||||
--border: #d0d7de;
|
||||
--shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||
--nav-height: 70px;
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'temple-red': '#9d2235',
|
||||
'temple-red-dark': '#7a1a29',
|
||||
'temple-cream': '#f8f7f2',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
* { 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;
|
||||
}
|
||||
|
||||
.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>
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<body class="font-sans bg-gray-50 text-gray-900">
|
||||
|
||||
<nav class="navbar">
|
||||
<a href="{% url 'kaauh_career' %}" class="nav-brand">
|
||||
<div class="nav-brand-icon">
|
||||
<!-- Navigation -->
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<span>{% trans "Careers" %}</span>
|
||||
<span class="hidden md:inline">{% trans "Careers" %}</span>
|
||||
</a>
|
||||
|
||||
<div class="nav-actions">
|
||||
<div class="flex items-center gap-4">
|
||||
{% 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 %}
|
||||
|
||||
<form action="{% url 'set_language' %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
{% if LANGUAGE_CODE == 'en' %}
|
||||
<button name="language" value="ar" class="btn-lang" type="submit">
|
||||
🇸🇦 <span class="d-mobile-none">العربية</span>
|
||||
<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="hidden md:inline">العربية</span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button name="language" value="en" class="btn-lang" type="submit">
|
||||
🇺🇸 <span class="d-mobile-none">English</span>
|
||||
<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="hidden md:inline">English</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="dropdown" id="userDropdown">
|
||||
<button class="dropdown-trigger" onclick="toggleDropdown()">
|
||||
<div class="dropdown relative" id="userDropdown">
|
||||
<button class="bg-none border-none cursor-pointer flex items-center" onclick="toggleDropdown()">
|
||||
{% 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 %}
|
||||
<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 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</button>
|
||||
|
||||
<div class="dropdown-menu">
|
||||
<div class="dropdown-header">
|
||||
<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="p-4 border-b border-gray-200 flex items-center gap-3">
|
||||
<div>
|
||||
<div style="font-weight: 600;">{{ 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="font-semibold text-gray-900">{{ user.get_full_name|default:user.username }}</div>
|
||||
<div class="text-sm text-gray-500">{{ user.email|truncatechars:22 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="{% url 'applicant_portal_dashboard' %}" class="dropdown-item">
|
||||
<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>
|
||||
<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 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" %}
|
||||
</a>
|
||||
|
||||
<a href="{% url 'user_detail' request.user.pk %}" class="dropdown-item">
|
||||
<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>
|
||||
<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 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" %}
|
||||
</a>
|
||||
|
||||
<form method="post" action="{% url 'account_logout'%}">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="dropdown-item logout">
|
||||
<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>
|
||||
<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 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" %}
|
||||
</button>
|
||||
</form>
|
||||
@ -282,15 +101,16 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Alert Messages -->
|
||||
{% if messages %}
|
||||
<div class="alert-container">
|
||||
<div class="max-w-5xl mx-auto my-4 px-4">
|
||||
{% for message in messages %}
|
||||
<div class="alert">
|
||||
<div 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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
<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 class="flex items-center gap-3">
|
||||
<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 }}
|
||||
</div>
|
||||
<button onclick="this.parentElement.remove()" style="background:none; border:none; cursor:pointer; font-size: 1.25rem;">×</button>
|
||||
<button onclick="this.parentElement.remove()" class="bg-none border-none cursor-pointer text-xl">×</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@ -300,6 +120,10 @@
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.dropdown.active .dropdown-menu { display: block; }
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function toggleDropdown() {
|
||||
document.getElementById('userDropdown').classList.toggle('active');
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{% load static i18n %}
|
||||
{% load logo_tags %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
@ -93,7 +94,7 @@
|
||||
}
|
||||
|
||||
/* 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 {
|
||||
padding-bottom: calc(1.5rem + env(safe-area-inset-bottom));
|
||||
}
|
||||
@ -272,10 +273,12 @@
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- Sidebar Header -->
|
||||
<div class="p-4 sm:p-6 flex items-center gap-3 text-white sidebar-header safe-area-padding-top">
|
||||
<div class="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>
|
||||
</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()"
|
||||
class="lg:hidden ml-auto text-gray-400 hover:text-white transition p-2 -mr-2"
|
||||
aria-label="{% trans 'Close menu' %}">
|
||||
|
||||
@ -4,156 +4,40 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="container py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 pb-2 border-bottom border-primary">
|
||||
<h1 class="h3 mb-0 fw-bold text-primary">
|
||||
<i class="fas fa-file-alt me-2"></i>Create Form Template
|
||||
</h1>
|
||||
<a href="{% url 'form_templates_list' %}" class="btn btn-secondary btn-sm">
|
||||
<i class="fas fa-arrow-left me-1"></i>Back to Templates
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||
<div class="flex-1">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="h5 mb-0"><i class="fas fa-plus-circle me-2"></i>New Form Template</h3>
|
||||
<div class="flex justify-center">
|
||||
<div class="w-full max-w-4xl">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="bg-gradient-to-br from-temple-red/5 to-transparent border-b border-gray-100 px-6 py-4">
|
||||
<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 class="card-body p-4">
|
||||
<form method="post" id="createFormTemplate">
|
||||
<div class="p-6">
|
||||
<form method="post" id="createFormTemplate" class="space-y-6">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'form_templates_list' %}" class="btn btn-secondary">
|
||||
<i class="fas fa-times me-1"></i>Cancel
|
||||
<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="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 data-lucide="x" class="w-4 h-4"></i> Cancel
|
||||
</a>
|
||||
<button type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-save me-1"></i>Create Template
|
||||
<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 data-lucide="save" class="w-4 h-4"></i> Create Template
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -165,9 +49,9 @@
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<i class="fas fa-check-circle me-2"></i>{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
<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 data-lucide="check-circle" class="w-5 h-5 text-emerald-600"></i>
|
||||
<span class="text-sm font-medium">{{ message }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
@ -175,7 +59,6 @@
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Add form validation
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('createFormTemplate');
|
||||
|
||||
@ -203,6 +86,8 @@
|
||||
this.classList.remove('is-invalid');
|
||||
}
|
||||
});
|
||||
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<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="resume">{% trans "Resume" %}</option>
|
||||
<option value="cover_letter">{% trans "Cover Letter" %}</option>
|
||||
@ -17,13 +17,13 @@
|
||||
|
||||
<div>
|
||||
<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>
|
||||
<label for="documentFile" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Choose File" %}</label>
|
||||
<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">
|
||||
<i data-lucide="upload" class="w-5 h-5 text-gray-400"></i>
|
||||
</div>
|
||||
@ -34,17 +34,13 @@
|
||||
|
||||
<div class="mt-6 pt-4 border-t border-gray-200">
|
||||
<button type="button"
|
||||
onclick="this.closest('form').parentElement.parentElement.classList.add('hidden')"
|
||||
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">
|
||||
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"
|
||||
data-modal="documentUploadModal">
|
||||
{% trans "Cancel" %}
|
||||
</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>
|
||||
{% trans "Upload" %}
|
||||
</button>
|
||||
</div>
|
||||
</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
@ -5,85 +5,56 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ form.title }} - Embed</title>
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Font Awesome -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
|
||||
<script>
|
||||
tailwind.config = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'temple-red': '#9d2235',
|
||||
'temple-dark': '#1a1a1a',
|
||||
'temple-cream': '#f8f7f2',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
|
||||
body {
|
||||
background: #f8f9fa;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
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);
|
||||
font-family: 'Inter', sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #2d3748;
|
||||
color: #e2e8f0;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1.25rem;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||
font-size: 14px;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
margin-top: 15px;
|
||||
margin-top: 0.875rem;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
top: 0.625rem;
|
||||
right: 0.625rem;
|
||||
background: #4a5568;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-size: 0.75rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
@ -94,207 +65,121 @@
|
||||
.copy-btn.copied {
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
<div class="embed-container">
|
||||
<div class="embed-form-wrapper">
|
||||
<div class="embed-form">
|
||||
<!-- Header -->
|
||||
<div class="embed-info">
|
||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||
<div>
|
||||
<h1 class="h2 mb-2">
|
||||
<i class="fas fa-code text-primary"></i> Embed Form
|
||||
</h1>
|
||||
<p class="text-muted mb-0">Get the embed code for "{{ form.title }}"</p>
|
||||
</div>
|
||||
<a href="{% url 'form_preview' form.id %}" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="fas fa-external-link-alt"></i> Preview Form
|
||||
</a>
|
||||
<body class="bg-gray-100 text-gray-800 min-h-screen">
|
||||
<div class="p-4 md:p-6">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="bg-white rounded-2xl shadow-md p-6 mb-6">
|
||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 mb-2 flex items-center gap-2">
|
||||
<i data-lucide="code-2" class="w-7 h-7 text-temple-red"></i>
|
||||
Embed Form
|
||||
</h1>
|
||||
<p class="text-gray-600">Get embed code for "{{ form.title }}"</p>
|
||||
</div>
|
||||
<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 data-lucide="external-link" class="w-4 h-4"></i> Preview Form
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="row text-center mb-4">
|
||||
<div class="col-md-3">
|
||||
<div class="p-3">
|
||||
<h4 class="text-primary mb-1">{{ form.submissions.count }}</h4>
|
||||
<small class="text-muted">Submissions</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="p-3">
|
||||
<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 %}
|
||||
{{ form.structure.wizards.0.fields|length|default:0 }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</h4>
|
||||
<small class="text-muted">Fields</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="p-3">
|
||||
<h4 class="text-warning mb-1">{{ form.created_at|date:"M d" }}</h4>
|
||||
<small class="text-muted">Created</small>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Stats -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-center mb-6">
|
||||
<div class="p-4 bg-gray-50 rounded-xl">
|
||||
<div class="text-2xl font-bold text-temple-red">{{ form.submissions.count }}</div>
|
||||
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Submissions</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 class="p-4 bg-gray-50 rounded-xl">
|
||||
<div class="text-2xl font-bold text-blue-600">
|
||||
{% if form.structure.wizards %}
|
||||
{{ form.structure.wizards.0.fields|length|default:0 }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Fields</div>
|
||||
</div>
|
||||
<div class="p-4 bg-gray-50 rounded-xl">
|
||||
<div class="text-2xl font-bold text-amber-600">{{ form.created_at|date:"M d" }}</div>
|
||||
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Created</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs -->
|
||||
<ul class="nav nav-tabs" id="embedTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="iframe-tab" data-bs-toggle="tab" data-bs-target="#iframe" type="button" role="tab">
|
||||
<i class="fas fa-globe"></i> iFrame
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="popup-tab" data-bs-toggle="tab" data-bs-target="#popup" type="button" role="tab">
|
||||
<i class="fas fa-external-link-alt"></i> Popup
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="inline-tab" data-bs-toggle="tab" data-bs-target="#inline" type="button" role="tab">
|
||||
<i class="fas fa-code"></i> Inline
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Tabs -->
|
||||
<div class="flex border-b border-gray-200 mb-6">
|
||||
<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"
|
||||
data-tab="iframe"
|
||||
onclick="switchTab('iframe')">
|
||||
<i data-lucide="globe" class="w-4 h-4"></i> iFrame
|
||||
</button>
|
||||
<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"
|
||||
data-tab="popup"
|
||||
onclick="switchTab('popup')">
|
||||
<i data-lucide="external-link" class="w-4 h-4"></i> Popup
|
||||
</button>
|
||||
<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"
|
||||
data-tab="inline"
|
||||
onclick="switchTab('inline')">
|
||||
<i data-lucide="code" class="w-4 h-4"></i> Inline
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="embedTabsContent">
|
||||
<!-- iFrame Tab -->
|
||||
<div class="tab-pane fade show active" id="iframe" role="tabpanel">
|
||||
<h5 class="mb-3">iFrame Embed Code</h5>
|
||||
<p class="text-muted">Embed this form directly into your website using an iframe.</p>
|
||||
<!-- Tab Contents -->
|
||||
<div id="tab-content">
|
||||
<!-- iFrame Tab -->
|
||||
<div class="tab-pane" id="iframe-pane">
|
||||
<h5 class="text-lg font-semibold text-gray-900 mb-3">iFrame Embed Code</h5>
|
||||
<p class="text-gray-600 mb-4">Embed this form directly into your website using an iframe.</p>
|
||||
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" onclick="copyToClipboard(this, 'iframe-code')">
|
||||
<i class="fas fa-copy"></i> Copy
|
||||
</button>
|
||||
<code id="iframe-code"><iframe src="{{ request.build_absolute_uri }}{% url 'form_preview' form.id %}?embed=true"
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" onclick="copyToClipboard(this, 'iframe-code')">
|
||||
<i data-lucide="copy" class="w-3 h-3"></i> Copy
|
||||
</button>
|
||||
<code id="iframe-code"><iframe src="{{ request.build_absolute_uri }}{% url 'form_preview' form.id %}?embed=true"
|
||||
width="100%"
|
||||
height="600"
|
||||
frameborder="0"
|
||||
style="border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);"></iframe></code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dimensions-info">
|
||||
<h6 class="mb-3">Responsive Options</h6>
|
||||
<div class="responsive-options">
|
||||
<div class="responsive-option active" onclick="selectResponsive(this, 'iframe', 'fixed')">
|
||||
<strong>Fixed Height:</strong> 600px
|
||||
<div class="text-muted small">Best for most websites</div>
|
||||
</div>
|
||||
<div class="responsive-option" onclick="selectResponsive(this, 'iframe', 'responsive')">
|
||||
<strong>Responsive:</strong> Auto height
|
||||
<div class="text-muted small">Adjusts to content height</div>
|
||||
</div>
|
||||
<div class="responsive-option" onclick="selectResponsive(this, 'iframe', 'full')">
|
||||
<strong>Full Screen:</strong> 100vh
|
||||
<div class="text-muted small">Takes full viewport height</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 rounded-xl p-4 mt-4">
|
||||
<h6 class="font-semibold text-gray-900 mb-3">Responsive Options</h6>
|
||||
<div class="space-y-2">
|
||||
<div class="responsive-option active bg-white border-2 border-temple-red p-4 rounded-xl cursor-pointer hover:shadow-md transition"
|
||||
onclick="selectResponsive(this, 'iframe', 'fixed')">
|
||||
<div class="font-semibold">Fixed Height: 600px</div>
|
||||
<div class="text-sm text-gray-500">Best for most websites</div>
|
||||
</div>
|
||||
<div class="responsive-option bg-white border-2 border-gray-200 p-4 rounded-xl cursor-pointer hover:border-temple-red transition"
|
||||
onclick="selectResponsive(this, 'iframe', 'responsive')">
|
||||
<div class="font-semibold">Responsive: Auto height</div>
|
||||
<div class="text-sm text-gray-500">Adjusts to content height</div>
|
||||
</div>
|
||||
<div class="responsive-option bg-white border-2 border-gray-200 p-4 rounded-xl cursor-pointer hover:border-temple-red transition"
|
||||
onclick="selectResponsive(this, 'iframe', 'full')">
|
||||
<div class="font-semibold">Full Screen: 100vh</div>
|
||||
<div class="text-sm text-gray-500">Takes full viewport height</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Popup Tab -->
|
||||
<div class="tab-pane fade" id="popup" role="tabpanel">
|
||||
<h5 class="mb-3">Popup Embed Code</h5>
|
||||
<p class="text-muted">Add a button or link that opens the form in a modal popup.</p>
|
||||
<!-- Popup Tab -->
|
||||
<div class="tab-pane hidden" id="popup-pane">
|
||||
<h5 class="text-lg font-semibold text-gray-900 mb-3">Popup Embed Code</h5>
|
||||
<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">
|
||||
<button class="copy-btn" onclick="copyToClipboard(this, 'popup-code')">
|
||||
<i class="fas fa-copy"></i> Copy
|
||||
</button>
|
||||
<code id="popup-code"><button onclick="openFormPopup()" class="btn btn-primary">
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" onclick="copyToClipboard(this, 'popup-code')">
|
||||
<i data-lucide="copy" class="w-3 h-3"></i> Copy
|
||||
</button>
|
||||
<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
|
||||
</button>
|
||||
|
||||
@ -317,107 +202,143 @@ function openFormPopup() {
|
||||
};
|
||||
}
|
||||
</script></code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h6>Customization Options</h6>
|
||||
<ul class="feature-list">
|
||||
<li><i class="fas fa-check"></i> Custom button text and styling</li>
|
||||
<li><i class="fas fa-check"></i> Trigger on page load or scroll</li>
|
||||
<li><i class="fas fa-check"></i> Custom modal dimensions</li>
|
||||
<li><i class="fas fa-check"></i> Close on outside click</li>
|
||||
</ul>
|
||||
<div class="mt-6">
|
||||
<h6 class="font-semibold text-gray-900 mb-3">Customization Options</h6>
|
||||
<ul class="space-y-2">
|
||||
<li class="flex items-center gap-2 text-gray-700">
|
||||
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Custom button text and styling
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inline Tab -->
|
||||
<div class="tab-pane hidden" id="inline-pane">
|
||||
<h5 class="text-lg font-semibold text-gray-900 mb-3">Inline Embed Code</h5>
|
||||
<p class="text-gray-600 mb-4">Embed form HTML directly into your page for maximum customization.</p>
|
||||
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4 mb-4 flex items-start gap-3">
|
||||
<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>
|
||||
|
||||
<!-- Inline Tab -->
|
||||
<div class="tab-pane fade" id="inline" role="tabpanel">
|
||||
<h5 class="mb-3">Inline Embed Code</h5>
|
||||
<p class="text-muted">Embed the form HTML directly into your page for maximum customization.</p>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-info-circle"></i> <strong>Note:</strong> This option requires more technical knowledge but offers the best integration.
|
||||
</div>
|
||||
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" onclick="copyToClipboard(this, 'inline-code')">
|
||||
<i class="fas fa-copy"></i> Copy
|
||||
</button>
|
||||
<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://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
||||
<div class="code-block">
|
||||
<button class="copy-btn" onclick="copyToClipboard(this, 'inline-code')">
|
||||
<i data-lucide="copy" class="w-3 h-3"></i> Copy
|
||||
</button>
|
||||
<code id="inline-code"><!-- Form CSS -->
|
||||
<link href="https://cdn.tailwindcss.com" rel="stylesheet">
|
||||
|
||||
<!-- Form Container -->
|
||||
<div id="form-{{ form.id }}">
|
||||
<div class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status"></div>
|
||||
<p class="mt-3">Loading form...</p>
|
||||
<div class="text-center py-12">
|
||||
<div class="inline-block w-8 h-8 border-4 border-temple-red border-t-transparent rounded-full animate-spin"></div>
|
||||
<p class="mt-4 text-gray-600">Loading form...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
// Load form data and render
|
||||
fetch('/recruitment/api/forms/{{ form.id }}/load/')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// Render form using the form structure
|
||||
// Render form using to form structure
|
||||
console.log('Form data:', data);
|
||||
// Implement form rendering logic here
|
||||
})
|
||||
.catch(error => console.error('Error loading form:', error));
|
||||
</script></code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<h6>Benefits of Inline Embed</h6>
|
||||
<ul class="feature-list">
|
||||
<li><i class="fas fa-check"></i> Full control over styling</li>
|
||||
<li><i class="fas fa-check"></i> Better SEO integration</li>
|
||||
<li><i class="fas fa-check"></i> Faster initial load</li>
|
||||
<li><i class="fas fa-check"></i> Custom form handling</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="mt-6">
|
||||
<h6 class="font-semibold text-gray-900 mb-3">Benefits of Inline Embed</h6>
|
||||
<ul class="space-y-2">
|
||||
<li class="flex items-center gap-2 text-gray-700">
|
||||
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Full control over styling
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview Section -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-eye"></i> Live Preview
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<iframe src="{% url 'form_preview' form.id %}?embed=true"
|
||||
class="preview-iframe"
|
||||
frameborder="0">
|
||||
</iframe>
|
||||
</div>
|
||||
<!-- Preview Section -->
|
||||
<div class="bg-white rounded-2xl shadow-md overflow-hidden mt-6">
|
||||
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||
<h5 class="font-semibold flex items-center gap-2">
|
||||
<i data-lucide="eye" class="w-5 h-5"></i> Live Preview
|
||||
</h5>
|
||||
</div>
|
||||
<div class="p-0">
|
||||
<iframe src="{% url 'form_preview' form.id %}?embed=true"
|
||||
class="w-full border border-gray-200 rounded-xl"
|
||||
style="height: 600px;"
|
||||
frameborder="0">
|
||||
</iframe>
|
||||
</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>
|
||||
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) {
|
||||
const element = document.getElementById(elementId);
|
||||
const text = element.textContent;
|
||||
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
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');
|
||||
lucide.createIcons();
|
||||
|
||||
setTimeout(function() {
|
||||
button.innerHTML = originalText;
|
||||
button.classList.remove('copied');
|
||||
lucide.createIcons();
|
||||
}, 2000);
|
||||
}).catch(function(err) {
|
||||
console.error('Failed to copy text: ', err);
|
||||
@ -431,12 +352,14 @@ function openFormPopup() {
|
||||
|
||||
try {
|
||||
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');
|
||||
lucide.createIcons();
|
||||
|
||||
setTimeout(function() {
|
||||
button.innerHTML = '<i class="fas fa-copy"></i> Copy';
|
||||
button.innerHTML = originalText;
|
||||
button.classList.remove('copied');
|
||||
lucide.createIcons();
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('Fallback copy failed: ', err);
|
||||
@ -450,13 +373,15 @@ function openFormPopup() {
|
||||
// Remove active class from all options in this tab
|
||||
const container = element.closest('.tab-pane');
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
@ -489,14 +414,6 @@ function openFormPopup() {
|
||||
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>
|
||||
</body>
|
||||
</html>
|
||||
@ -3,140 +3,143 @@
|
||||
{% block title %}Forms - University ATS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1><i class="fas fa-wpforms"></i> Forms</h1>
|
||||
<a href="{% url 'form_builder' %}" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> Create New Form
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" class="row g-3">
|
||||
<div class="col-md-8">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="search" placeholder="Search forms..." value="{{ request.GET.search }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">
|
||||
<i class="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select class="form-select" name="sort">
|
||||
<option value="-created_at">Latest First</option>
|
||||
<option value="created_at">Oldest First</option>
|
||||
<option value="title">Title (A-Z)</option>
|
||||
<option value="-title">Title (Z-A)</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||
<div class="flex-1">
|
||||
<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="layout-template" class="w-8 h-8 text-temple-red"></i>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<!-- Forms List -->
|
||||
{% if page_obj %}
|
||||
<div class="row">
|
||||
{% for form in page_obj %}
|
||||
<div class="col-lg-4 col-md-6 mb-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<h5 class="card-title mb-1">{{ form.title }}</h5>
|
||||
<span class="badge bg-success">Active</span>
|
||||
</div>
|
||||
|
||||
<p class="card-text text-muted small">
|
||||
{{ form.description|truncatewords:15 }}
|
||||
</p>
|
||||
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-user"></i> {{ form.created_by.username }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-calendar"></i> {{ form.created_at|date:"M d, Y" }}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-chart-bar"></i> {{ form.submissions.count }} submissions
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-transparent">
|
||||
<div class="btn-group w-100" role="group">
|
||||
{% if form.created_by == user %}
|
||||
<a href="{% url 'edit_form' form.slug %}" class="btn btn-sm btn-outline-warning">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'form_preview' form.slug %}" class="btn btn-sm btn-outline-primary" target="_blank">
|
||||
<i class="fas fa-eye"></i> Preview
|
||||
</a>
|
||||
<a href="{% url 'form_embed' form.slug %}" class="btn btn-sm btn-outline-secondary" target="_blank">
|
||||
<i class="fas fa-code"></i> Embed
|
||||
</a>
|
||||
<a href="{% url 'form_submissions' form.slug %}" class="btn btn-sm btn-outline-info">
|
||||
<i class="fas fa-list"></i> Submissions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<!-- Search and Filter -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-6">
|
||||
<div class="p-6">
|
||||
<form method="get" class="flex flex-col md:flex-row gap-4">
|
||||
<div class="flex-1">
|
||||
<div class="relative">
|
||||
<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="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-temple-red transition" type="submit">
|
||||
<i data-lucide="search" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<nav aria-label="Forms pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<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>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<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>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
|
||||
</li>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Next</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<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>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-wpforms fa-3x text-muted mb-3"></i>
|
||||
<h3>No forms found</h3>
|
||||
<p class="text-muted">Create your first form to get started.</p>
|
||||
<a href="{% url 'form_builder' %}" class="btn btn-primary">Create Form</a>
|
||||
<div class="md:w-64">
|
||||
<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">Oldest First</option>
|
||||
<option value="title">Title (A-Z)</option>
|
||||
<option value="-title">Title (Z-A)</option>
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<!-- Forms List -->
|
||||
{% if page_obj %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{% for form in page_obj %}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-start mb-3">
|
||||
<h5 class="text-lg font-bold text-gray-900 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>
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-600 mb-4 line-clamp-2">
|
||||
{{ form.description|truncatewords:15 }}
|
||||
</p>
|
||||
|
||||
<div class="space-y-2 mb-4">
|
||||
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||
<i data-lucide="user" class="w-4 h-4 text-gray-400"></i>
|
||||
{{ form.created_by.username }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||
<i data-lucide="calendar" class="w-4 h-4 text-gray-400"></i>
|
||||
{{ form.created_at|date:"M d, Y" }}
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||
<i data-lucide="bar-chart-3" class="w-4 h-4 text-gray-400"></i>
|
||||
{{ form.submissions.count }} submissions
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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 %}
|
||||
<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 data-lucide="edit-3" class="w-3 h-3"></i> Edit
|
||||
</a>
|
||||
{% endif %}
|
||||
<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 data-lucide="eye" class="w-3 h-3"></i> Preview
|
||||
</a>
|
||||
<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 data-lucide="code-2" class="w-3 h-3"></i> Embed
|
||||
</a>
|
||||
<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 data-lucide="list" class="w-3 h-3"></i> Submissions
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 mt-6">
|
||||
<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 %}
|
||||
<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">
|
||||
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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">
|
||||
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||
{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<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">
|
||||
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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">
|
||||
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||
<i data-lucide="layout-template" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">No forms found</h3>
|
||||
<p class="text-gray-500 mb-6">Create your first form to get started.</p>
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Add any interactive JavaScript here if needed
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -4,59 +4,60 @@
|
||||
|
||||
{% block content %}
|
||||
{% if not is_embed %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1><i class="fas fa-wpforms"></i> Form Preview</h1>
|
||||
<div>
|
||||
<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 class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||
<div class="flex-1">
|
||||
<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="layout-template" class="w-8 h-8 text-temple-red"></i>
|
||||
</div>
|
||||
</div>
|
||||
Form Preview
|
||||
</h1>
|
||||
</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>
|
||||
{% endif %}
|
||||
|
||||
<!-- Form Preview Container -->
|
||||
<div class="{% if is_embed %}embed-container{% else %}container-fluid{% endif %}">
|
||||
<div class="{% if is_embed %}embed-form-wrapper{% else %}row justify-content-center{% endif %}">
|
||||
<div class="{% if is_embed %}embed-form{% else %}col-lg-8 col-md-10{% 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 %}{% else %}flex justify-center{% endif %}">
|
||||
<div class="{% if is_embed %}w-full{% else %}w-full max-w-4xl{% endif %}">
|
||||
<!-- Form Header -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h3 class="mb-1">{{ form.title }}</h3>
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
|
||||
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-6">
|
||||
<h3 class="text-2xl font-bold mb-2">{{ form.title }}</h3>
|
||||
{% if form.description %}
|
||||
<p class="mb-0 opacity-90">{{ form.description }}</p>
|
||||
<p class="text-white/90 mb-0">{{ form.description }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-chart-bar"></i> {{ submission_count }} submissions
|
||||
</small>
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-calendar"></i> Created {{ form.created_at|date:"M d, Y" }}
|
||||
</small>
|
||||
<div class="p-6">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3">
|
||||
<span class="text-sm text-gray-600 flex items-center gap-2">
|
||||
<i data-lucide="bar-chart-3" class="w-4 h-4"></i> {{ submission_count }} submissions
|
||||
</span>
|
||||
<span class="text-sm text-gray-600 flex items-center gap-2">
|
||||
<i data-lucide="calendar" class="w-4 h-4"></i> Created {{ form.created_at|date:"M d, Y" }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Live Form Preview -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="p-0">
|
||||
<div id="form-preview-container">
|
||||
<!-- Form will be rendered here by Preact -->
|
||||
<div class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading form...</span>
|
||||
</div>
|
||||
<p class="mt-3 text-muted">Loading form preview...</p>
|
||||
<div class="text-center py-12">
|
||||
<div class="inline-block w-12 h-12 border-4 border-temple-red border-t-transparent rounded-full animate-spin"></div>
|
||||
<p class="mt-4 text-gray-500">Loading form preview...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -64,41 +65,35 @@
|
||||
|
||||
<!-- Form Analytics (only shown if not embedded) -->
|
||||
{% if not is_embed %}
|
||||
<div class="card shadow-sm mt-4">
|
||||
<div class="card-header">
|
||||
<h5><i class="fas fa-chart-line"></i> Form Analytics</h5>
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mt-6 overflow-hidden">
|
||||
<div class="border-b border-gray-100 p-6">
|
||||
<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 class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<h4 class="text-primary">{{ submission_count }}</h4>
|
||||
<small class="text-muted">Total Submissions</small>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||
<div class="text-center p-4 bg-gray-50 rounded-xl">
|
||||
<h4 class="text-2xl font-bold text-temple-red">{{ submission_count }}</h4>
|
||||
<span class="text-sm text-gray-600 mt-1 block">Total Submissions</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<h4 class="text-success">{{ form.created_at|timesince }}</h4>
|
||||
<small class="text-muted">Time Created</small>
|
||||
</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 class="col-md-3">
|
||||
<div class="text-center">
|
||||
<h4 class="text-info">{{ form.structure.wizards|length|default:0 }}</h4>
|
||||
<small class="text-muted">Form Steps</small>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-gray-50 rounded-xl">
|
||||
<h4 class="text-2xl font-bold text-blue-600">{{ form.structure.wizards|length|default:0 }}</h4>
|
||||
<span class="text-sm text-gray-600 mt-1 block">Form Steps</span>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="text-center">
|
||||
<h4 class="text-warning">
|
||||
{% if form.structure.wizards %}
|
||||
{{ form.structure.wizards.0.fields|length|default:0 }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</h4>
|
||||
<small class="text-muted">First Step Fields</small>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-gray-50 rounded-xl">
|
||||
<h4 class="text-2xl font-bold text-amber-600">
|
||||
{% if form.structure.wizards %}
|
||||
{{ form.structure.wizards.0.fields|length|default:0 }}
|
||||
{% else %}
|
||||
0
|
||||
{% endif %}
|
||||
</h4>
|
||||
<span class="text-sm text-gray-600 mt-1 block">First Step Fields</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -109,41 +104,47 @@
|
||||
</div>
|
||||
|
||||
<!-- Success Modal -->
|
||||
<div class="modal fade" id="successModal" tabindex="-1" aria-labelledby="successModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-success text-white">
|
||||
<h5 class="modal-title" id="successModalLabel">
|
||||
<i class="fas fa-check-circle"></i> Form Submitted Successfully!
|
||||
<div id="successModal" class="fixed inset-0 z-50 hidden">
|
||||
<div class="absolute inset-0 bg-black/50 transition-opacity" onclick="closeModal('successModal')"></div>
|
||||
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||
<div class="relative bg-white rounded-2xl shadow-xl max-w-md w-full p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<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>
|
||||
<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 class="modal-body">
|
||||
<p class="mb-0">Thank you for submitting the form. Your response has been recorded.</p>
|
||||
<div class="mb-6">
|
||||
<p class="text-gray-600 mb-0">Thank you for submitting this form. Your response has been recorded.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-primary" onclick="resetForm()">Submit Another Response</button>
|
||||
<div class="flex gap-3">
|
||||
<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" 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>
|
||||
|
||||
<!-- Error Modal -->
|
||||
<div class="modal fade" id="errorModal" tabindex="-1" aria-labelledby="errorModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-danger text-white">
|
||||
<h5 class="modal-title" id="errorModalLabel">
|
||||
<i class="fas fa-exclamation-triangle"></i> Submission Error
|
||||
<div id="errorModal" class="fixed inset-0 z-50 hidden">
|
||||
<div class="absolute inset-0 bg-black/50 transition-opacity" onclick="closeModal('errorModal')"></div>
|
||||
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||
<div class="relative bg-white rounded-2xl shadow-xl max-w-md w-full p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<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>
|
||||
<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 class="modal-body">
|
||||
<p class="mb-0" id="errorMessage">An error occurred while submitting the form. Please try again.</p>
|
||||
<div class="mb-6">
|
||||
<p class="text-gray-600 mb-0" id="errorMessage">An error occurred while submitting the form. Please try again.</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<div class="flex gap-3">
|
||||
<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>
|
||||
@ -153,36 +154,249 @@
|
||||
{% block extra_css %}
|
||||
{% if is_embed %}
|
||||
<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 {
|
||||
background: #f8f9fa;
|
||||
background: #f3f4f6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
{% 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 %}
|
||||
|
||||
{% block extra_js %}
|
||||
@ -440,7 +654,7 @@ class PreviewRatingField extends Component {
|
||||
<div class="rating-container">
|
||||
${[1, 2, 3, 4, 5].map(n => html`
|
||||
<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)}>
|
||||
★
|
||||
</span>
|
||||
@ -590,16 +804,14 @@ class FormPreview extends Component {
|
||||
|
||||
if (result.success) {
|
||||
this.setState({ isSubmitted: true });
|
||||
const modal = new bootstrap.Modal(document.getElementById('successModal'));
|
||||
modal.show();
|
||||
openModal('successModal');
|
||||
} else {
|
||||
throw new Error(result.error || 'Submission failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Submission error:', error);
|
||||
document.getElementById('errorMessage').textContent = error.message;
|
||||
const modal = new bootstrap.Modal(document.getElementById('errorModal'));
|
||||
modal.show();
|
||||
openModal('errorModal');
|
||||
} finally {
|
||||
this.setState({ isSubmitting: false });
|
||||
}
|
||||
@ -621,10 +833,12 @@ class FormPreview extends Component {
|
||||
|
||||
if (this.state.isSubmitted) {
|
||||
return html`
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-check-circle text-success fa-4x mb-3"></i>
|
||||
<h3>Thank You!</h3>
|
||||
<p class="text-muted">Your form has been submitted successfully.</p>
|
||||
<div class="text-center py-12">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 bg-emerald-100 rounded-full mb-4">
|
||||
<i data-lucide="check-circle" class="w-8 h-8 text-emerald-600"></i>
|
||||
</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}>
|
||||
Submit Another Response
|
||||
</button>
|
||||
@ -633,10 +847,10 @@ class FormPreview extends Component {
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="form-preview">
|
||||
<div class="form-preview p-6">
|
||||
<!-- Progress Bar -->
|
||||
${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"
|
||||
style="width: ${this.getProgress()}%; transition: width 0.3s ease;">
|
||||
</div>
|
||||
@ -645,7 +859,7 @@ class FormPreview extends Component {
|
||||
|
||||
<!-- Wizard Title -->
|
||||
${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 -->
|
||||
@ -661,25 +875,25 @@ class FormPreview extends Component {
|
||||
</form>
|
||||
|
||||
<!-- 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"
|
||||
class="btn btn-outline-secondary"
|
||||
class="btn btn-outline-secondary w-full sm:w-auto"
|
||||
onClick=${this.handlePrevious}
|
||||
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 type="button"
|
||||
class="btn btn-primary"
|
||||
class="btn btn-primary w-full sm:w-auto"
|
||||
onClick=${this.handleNext}
|
||||
disabled=${this.state.isSubmitting}>
|
||||
${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...
|
||||
` : ''}
|
||||
${this.state.currentWizardIndex === this.getTotalWizards() - 1 ?
|
||||
html`<i class="fas fa-check"></i> Submit` :
|
||||
html`Next <i class="fas fa-arrow-right"></i>`
|
||||
html`<i data-lucide="check" class="w-4 h-4"></i> Submit` :
|
||||
html`Next <i data-lucide="arrow-right" class="w-4 h-4"></i>`
|
||||
}
|
||||
</button>
|
||||
</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
|
||||
if (typeof FilePond !== 'undefined') {
|
||||
FilePond.registerPlugin(
|
||||
@ -696,11 +943,12 @@ if (typeof FilePond !== 'undefined') {
|
||||
);
|
||||
}
|
||||
|
||||
// Render the form preview
|
||||
// Render form preview
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const container = document.getElementById('form-preview-container');
|
||||
if (container) {
|
||||
render(html`<${FormPreview} />`, container);
|
||||
lucide.createIcons();
|
||||
}
|
||||
});
|
||||
|
||||
@ -709,7 +957,11 @@ window.resetForm = function() {
|
||||
const container = document.getElementById('form-preview-container');
|
||||
if (container) {
|
||||
render(html`<${FormPreview} />`, container);
|
||||
lucide.createIcons();
|
||||
}
|
||||
};
|
||||
|
||||
window.closeModal = closeModal;
|
||||
window.openModal = openModal;
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,201 +1,67 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
{% load form_filters %}
|
||||
{% load i18n static form_filters %}
|
||||
|
||||
{% 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 %}
|
||||
|
||||
<div class="container-fluid py-4">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="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>
|
||||
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}" class="text-secondary text-decoration-none">{% trans "Form Template" %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page" style="
|
||||
color: #F43B5E; /* Rosy Accent Color */
|
||||
font-weight: 600;
|
||||
">{% trans "Submission Details" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2 class="text-primary fw-bold">{% trans "Submission Details" %}</h2>
|
||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- Breadcrumb -->
|
||||
<nav class="mb-6" aria-label="breadcrumb">
|
||||
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||
<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="text-gray-400">/</li>
|
||||
<li><a href="{% url 'form_builder' submission.template.pk%}" class="text-gray-500 hover:text-temple-red transition">{% trans "Form Template" %}</a></li>
|
||||
<li class="text-temple-red font-semibold">{% trans "Submission Details" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||
<div class="flex-1">
|
||||
<h2 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-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>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{% trans "Submission Metadata" %}</h5>
|
||||
<!-- Submission Metadata -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-6 overflow-hidden">
|
||||
<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 class="card-body small">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-4">
|
||||
<i class="fas fa-fingerprint me-2 text-primary"></i>
|
||||
<strong>{% trans "Submission ID:" %}</strong> <span class="text-secondary">{{ submission.id }}</span>
|
||||
<div class="p-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="fingerprint" class="w-5 h-5 text-temple-red"></i>
|
||||
<span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Submission ID:" %}</strong> {{ submission.id }}</span>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<i class="fas fa-calendar-check me-2 text-primary"></i>
|
||||
<strong>{% trans "Submitted:" %}</strong> <span class="text-secondary">{{ submission.submitted_at|date:"M d, Y H:i" }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="calendar-check" class="w-5 h-5 text-temple-red"></i>
|
||||
<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 class="col-md-4">
|
||||
<i class="fas fa-file-alt me-2 text-primary"></i>
|
||||
<strong>{% trans "Form:" %}</strong> <span class="text-secondary">{{ submission.template.name }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
|
||||
<span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Form:" %}</strong> {{ submission.template.name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% 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 %}
|
||||
<div class="col-md-4">
|
||||
<i class="fas fa-user me-2 text-primary"></i>
|
||||
<strong>{% trans "Applicant Name:" %}</strong> <span class="text-secondary">{{ submission.applicant_name }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="user" class="w-5 h-5 text-temple-red"></i>
|
||||
<span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Applicant Name:" %}</strong> {{ submission.applicant_name }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if submission.applicant_email %}
|
||||
<div class="col-md-4">
|
||||
<i class="fas fa-envelope me-2 text-primary"></i>
|
||||
<strong>{% trans "Email:" %}</strong> <span class="text-secondary">{{ submission.applicant_email }}</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="mail" class="w-5 h-5 text-temple-red"></i>
|
||||
<span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Email:" %}</strong> {{ submission.applicant_email }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -203,71 +69,79 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">{% trans "Form Responses" %}</h5>
|
||||
<!-- Form Responses -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<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 class="card-body p-0">
|
||||
<div class="p-0">
|
||||
{% with submission=submission %}
|
||||
{% get_all_responses_flat submission as flat_responses %}
|
||||
|
||||
{% if flat_responses %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-submission table-hover">
|
||||
<thead>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<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 %}
|
||||
<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 %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
<!-- Response Value Row -->
|
||||
<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 %}
|
||||
<td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700 max-w-[300px]">
|
||||
{% if response.uploaded_file %}
|
||||
<div>
|
||||
<span class="d-block text-truncate" style="max-width: 180px;"><i class="fas fa-file me-1"></i> {{ response.uploaded_file.name }}</span>
|
||||
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-secondary mt-1" target="_blank" title="{% trans 'Download File' %}">
|
||||
<i class="fas fa-download"></i> {% trans "Download" %}
|
||||
<div class="space-y-2">
|
||||
<span class="block text-sm text-gray-600 truncate max-w-[180px] inline-flex items-center gap-1">
|
||||
<i data-lucide="file" class="w-4 h-4"></i> {{ response.uploaded_file.name }}
|
||||
</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>
|
||||
</div>
|
||||
{% elif response.value %}
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
{% else %}
|
||||
<span class="text-muted small">{% trans "Not provided" %}</span>
|
||||
<span class="text-sm text-gray-400">{% trans "Not provided" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
<!-- Associated Stage Row -->
|
||||
<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 %}
|
||||
<td>
|
||||
<span class="small text-secondary">{{ response.stage_name|default:"N/A" }}</span>
|
||||
<td class="px-6 py-4 text-sm text-gray-600">
|
||||
{{ response.stage_name|default:"N/A" }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{% trans "Field Required" %}</strong></td>
|
||||
<!-- Field Required Row -->
|
||||
<tr>
|
||||
<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 %}
|
||||
<td>
|
||||
<td class="px-6 py-4">
|
||||
{% 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 %}
|
||||
<span class="small text-success">{% trans "No" %}</span>
|
||||
<span class="text-sm text-emerald-600 font-semibold">{% trans "No" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
@ -276,14 +150,18 @@
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center text-muted py-5 px-3">
|
||||
<i class="fas fa-exclamation-circle fa-2x mb-3"></i>
|
||||
<p class="lead">{% 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>
|
||||
<div class="text-center py-12 px-6">
|
||||
<i data-lucide="alert-circle" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||
<p class="text-lg font-semibold text-gray-900 mb-2">{% trans "No response fields were found for this submission." %}</p>
|
||||
<p class="text-sm text-gray-500">{% trans "This may occur if the form template was modified or responses were cleared." %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -2,373 +2,177 @@
|
||||
{% load static i18n form_filters %}
|
||||
|
||||
|
||||
{% block title %}{% trans "All Submissions for" %} {{ template.name }} - ATS{% 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 title %}{% trans "All Submissions for" %} {{ template.name }} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary text-decoration-none">{% trans "Dashboard" %}</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary text-decoration-none">{% trans "Form Templates" %}</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'form_template_submissions_list' template.slug %}" class="text-secondary text-decoration-none">{% trans "Submissions" %}</a></li>
|
||||
<li class="breadcrumb-item active" style="
|
||||
color: #F43B5E;
|
||||
font-weight: 600;
|
||||
">{% trans "All Submissions Table" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="space-y-6">
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-1 d-flex align-items-center">
|
||||
<i class="fas fa-table me-2"></i>
|
||||
{% trans "All Submissions for" %}: <span class="text-white ms-2">{{ template.name }}</span>
|
||||
<!-- 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 '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 "Dashboard" %}
|
||||
</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>
|
||||
</nav>
|
||||
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div class="flex-1">
|
||||
<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="table" class="w-8 h-8 text-temple-red"></i>
|
||||
</div>
|
||||
{% trans "All Submissions for" %}: {{ template.name }}
|
||||
</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>
|
||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-light btn-sm">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
|
||||
<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>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if page_obj.object_list %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Submission ID" %}</th>
|
||||
<th scope="col">{% trans "Applicant Name" %}</th>
|
||||
<th scope="col">{% trans "Applicant Email" %}</th>
|
||||
<th scope="col">{% trans "Submitted At" %}</th>
|
||||
{% for field in fields %}
|
||||
<th scope="col">{{ field.label }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for submission in page_obj %}
|
||||
<tr>
|
||||
<td class="fw-medium">{{ submission.id }}</td>
|
||||
<td>{{ submission.applicant_name|default:"N/A" }}</td>
|
||||
<td>{{ submission.applicant_email|default:"N/A" }}</td>
|
||||
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
||||
{% for field in fields %}
|
||||
{% get_field_response_for_submission submission field as response %}
|
||||
<td class="response-value">
|
||||
{% if response %}
|
||||
{% if response.uploaded_file %}
|
||||
<div class="file-response">
|
||||
</div>
|
||||
|
||||
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-primary" target="_blank" title="Download File">
|
||||
<i class="fas fa-download"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% elif response.value %}
|
||||
{% if response.field.field_type == 'checkbox' and response.value|length > 0 %}
|
||||
<div>
|
||||
{% for val in response.value|to_list %}
|
||||
<span class="badge bg-secondary badge-response">{{ val }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elif response.field.field_type == 'radio' or response.field.field_type == 'select' %}
|
||||
<span class="badge bg-info">{{ response.value }}</span>
|
||||
{% else %}
|
||||
<p class="mb-0">{{ response.value|linebreaksbr|truncatewords:10 }}</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-muted">Not provided</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-muted">Not provided</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 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>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% 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="pagination-info mb-3 mb-md-0">
|
||||
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
||||
Showing {{ start }} to {{ end }} of {{ total }} results.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1" aria-label="First">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
|
||||
<span aria-hidden="true">‹</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="page-item active">
|
||||
<span class="page-link">
|
||||
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
|
||||
<span aria-hidden="true">›</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<h3 class="h5 mb-3">{% trans "No Submissions Found" %}</h3>
|
||||
<p class="text-muted mb-4">
|
||||
{% trans "There are no submissions for this form template yet." %}
|
||||
</p>
|
||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-main-action">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% 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 %}
|
||||
<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="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>
|
||||
<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" 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" 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" 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 %}
|
||||
<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 %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-100 text-sm">
|
||||
{% for submission in page_obj %}
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-4 py-2 font-semibold text-gray-900 whitespace-nowrap">{{ submission.id }}</td>
|
||||
<td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.applicant_name|default:"N/A" }}</td>
|
||||
<td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.applicant_email|default:"N/A" }}</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 %}
|
||||
{% get_field_response_for_submission submission field as response %}
|
||||
<td class="px-4 py-2 max-w-[200px]">
|
||||
{% if response %}
|
||||
{% if response.uploaded_file %}
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="{{ response.uploaded_file.url }}"
|
||||
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"
|
||||
target="_blank"
|
||||
title="{% trans 'Download File' %}">
|
||||
<i data-lucide="download" class="w-3 h-3"></i> {% trans "Download" %}
|
||||
</a>
|
||||
</div>
|
||||
{% elif response.value %}
|
||||
{% if response.field.field_type == 'checkbox' and response.value|length > 0 %}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{% for val in response.value|to_list %}
|
||||
<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 %}
|
||||
</div>
|
||||
{% elif response.field.field_type == 'radio' or response.field.field_type == 'select' %}
|
||||
<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 %}
|
||||
<p class="text-xs text-gray-700 line-clamp-2">{{ response.value|linebreaksbr|truncatewords:10 }}</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-xs text-gray-400">Not provided</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-xs text-gray-400">Not provided</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||
<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 %}
|
||||
Showing {{ start }} to {{ end }} of {{ total }} results.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<nav aria-label="Page navigation" class="flex items-center gap-2">
|
||||
{% if page_obj.has_previous %}
|
||||
<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">
|
||||
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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">
|
||||
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<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">
|
||||
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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">
|
||||
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- No Results -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||
<i data-lucide="inbox" 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 Submissions Found" %}</h3>
|
||||
<p class="text-gray-500 mb-6">{% trans "There are no submissions for this form template yet." %}</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">
|
||||
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Submissions" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,375 +1,182 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
{% load static i18n %}
|
||||
|
||||
|
||||
{% block title %}{% trans "Submissions for" %} {{ template.name }} - ATS{% 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 title %}{% trans "Submissions for" %} {{ template.name }} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% comment %} <div class="container py-4">
|
||||
<!-- Search and Filter Section -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" action="" class="w-100">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-12 col-md-5">
|
||||
<label for="search" class="form-label small text-muted">{% trans "Search by name or Email" %}</label>
|
||||
<div class="input-group">
|
||||
{% include 'includes/search_form.html' %}
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
|
||||
<!-- 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 '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 "Dashboard" %}
|
||||
</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 class="text-temple-red font-semibold">{% trans "Submissions" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<div class="flex-1">
|
||||
<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>
|
||||
<div class="col-12 col-md-2">
|
||||
<label for="date_from" class="form-label small text-muted">{% trans "From Date" %}</label>
|
||||
<input type="date" class="form-control" id="date_from" name="date_from" value="{{ request.GET.date_from }}">
|
||||
{% trans "Submissions for" %}: {{ template.name }}
|
||||
</h1>
|
||||
<p class="text-sm text-gray-500 mt-2">Template ID: #{{ template.id }}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<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 data-lucide="table" class="w-4 h-4"></i> {% trans "View All in Table" %}
|
||||
</a>
|
||||
<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 to Templates" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</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 %}
|
||||
<div id="form-template-submissions-list">
|
||||
{# View Switcher #}
|
||||
{% include "includes/_list_view_switcher.html" with list_id="form-template-submissions-list" %}
|
||||
|
||||
{# Table View (Default) #}
|
||||
<div class="table-view active 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 "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>
|
||||
<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" 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" 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" 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="px-6 py-3 text-right text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-100">
|
||||
{% for submission in page_obj %}
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-6 py-4 font-semibold text-gray-900">{{ submission.id }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">{{ submission.applicant_name|default:"N/A" }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">{{ submission.applicant_email|default:"N/A" }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
||||
<td class="px-6 py-4 text-right">
|
||||
<a href="{% url 'form_submission_details' template_id=submission.template.id slug=submission.slug %}"
|
||||
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>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Card View #}
|
||||
<div class="card-view lg:hidden grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{% for submission in page_obj %}
|
||||
<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">
|
||||
<h3 class="h5 font-bold mb-1">{% trans "Submission" %} #{{ submission.id }}</h3>
|
||||
<small class="text-white/70">{{ template.name }}</small>
|
||||
</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 class="p-4 space-y-2">
|
||||
<p class="text-sm text-gray-700">
|
||||
<span class="font-semibold">{% trans "Applicant Name" %}:</span> {{ submission.applicant_name|default:"N/A" }}<br>
|
||||
<span class="font-semibold">{% trans "Applicant Email" %}:</span> {{ submission.applicant_email|default:"N/A" }}<br>
|
||||
<span class="font-semibold">{% trans "Submitted At" %}:</span> {{ submission.submitted_at|date:"M d, Y H:i" }}
|
||||
</p>
|
||||
</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" %}
|
||||
<div class="p-4 pt-0">
|
||||
<a href="{% url 'form_submission_details' template_id=template.id slug=submission.slug %}"
|
||||
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>
|
||||
</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>
|
||||
</nav>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h1 class="h3 mb-1 d-flex align-items-center">
|
||||
<i class="fas fa-file-alt me-2"></i>
|
||||
{% trans "Submissions for" %}: <span class="text-white ms-2">{{ template.name }}</span>
|
||||
</h1>
|
||||
<small class="text-white-50">Template ID: #{{ template.id }}</small>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'form_template_all_submissions' template.id %}" class="btn btn-outline-light btn-sm">
|
||||
<i class="fas fa-table me-1"></i> {% trans "View All in Table" %}
|
||||
</a>
|
||||
<a href="{% url 'form_templates_list' %}" class="btn btn-outline-light btn-sm">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Templates" %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if page_obj.object_list %}
|
||||
<div id="form-template-submissions-list">
|
||||
{# View Switcher #}
|
||||
{% include "includes/_list_view_switcher.html" with list_id="form-template-submissions-list" %}
|
||||
|
||||
{# Table View (Default) #}
|
||||
<div class="table-view active">
|
||||
<div class="table-responsive mb-4">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Submission ID" %}</th>
|
||||
<th scope="col">{% trans "Applicant Name" %}</th>
|
||||
<th scope="col">{% trans "Applicant Email" %}</th>
|
||||
<th scope="col">{% trans "Submitted At" %}</th>
|
||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for submission in page_obj %}
|
||||
<tr>
|
||||
<td class="fw-medium">{{ submission.id }}</td>
|
||||
<td>{{ submission.applicant_name|default:"N/A" }}</td>
|
||||
<td>{{ submission.applicant_email|default:"N/A" }}</td>
|
||||
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
||||
<td class="text-end">
|
||||
<a href="{% url 'form_submission_details' template_id=submission.template.id slug=submission.slug %}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Card View #}
|
||||
<div class="card-view">
|
||||
<div class="row g-4">
|
||||
{% for submission in page_obj %}
|
||||
<div class="col-md-6 col-lg-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h3 class="h5 mb-2">{% trans "Submission" %} #{{ submission.id }}</h3>
|
||||
<small class="text-white-50">{{ template.name }}</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">
|
||||
<strong>{% trans "Applicant Name" %}:</strong> {{ submission.applicant_name|default:"N/A" }}<br>
|
||||
<strong>{% trans "Applicant Email" %}:</strong> {{ submission.applicant_email|default:"N/A" }}<br>
|
||||
<strong>{% trans "Submitted At" %}:</strong> {{ submission.submitted_at|date:"M d, Y H:i" }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<a href="{% url 'form_submission_details' template_id=template.id slug=submission.slug %}" class="btn btn-sm btn-outline-primary w-100">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||
<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 %}
|
||||
Showing {{ start }} to {{ end }} of {{ total }} results.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<nav aria-label="Page navigation" class="flex items-center gap-2">
|
||||
{% if page_obj.has_previous %}
|
||||
<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">
|
||||
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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">
|
||||
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center">
|
||||
<div class="pagination-info mb-3 mb-md-0">
|
||||
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
||||
Showing {{ start }} to {{ end }} of {{ total }} results.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination pagination-sm mb-0">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1" aria-label="First">
|
||||
<span aria-hidden="true">«</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
|
||||
<span aria-hidden="true">‹</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
|
||||
<li class="page-item active">
|
||||
<span class="page-link">
|
||||
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
|
||||
<span aria-hidden="true">›</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<h3 class="h5 mb-3">{% trans "No Submissions Found" %}</h3>
|
||||
<p class="text-muted mb-4">
|
||||
{% trans "There are no submissions for this form template yet." %}
|
||||
</p>
|
||||
<a href="{% url 'form_templates_list' %}" class="btn btn-main-action">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Templates" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if page_obj.has_next %}
|
||||
<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">
|
||||
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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">
|
||||
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<!-- No Results -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||
<i data-lucide="inbox" 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 Submissions Found" %}</h3>
|
||||
<p class="text-gray-500 mb-6">{% trans "There are no submissions for this form template yet." %}</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">
|
||||
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Templates" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -4,189 +4,107 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-file-alt me-2"></i>{% trans "Form Templates" %}
|
||||
</h1>
|
||||
<button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#createTemplateModal">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Create New Template" %}
|
||||
</button>
|
||||
<div class="space-y-6">
|
||||
|
||||
<!-- 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 '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>
|
||||
<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"
|
||||
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>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Filters -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden mb-6">
|
||||
<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 "Filter Templates" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<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...' %}"
|
||||
value="{{ query|default_if_none:'' }}">
|
||||
</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" %}
|
||||
</button>
|
||||
{% if query %}
|
||||
<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 data-lucide="x" class="w-4 h-4"></i> {% trans "Clear" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Search/Filter Area - Matching Standard Structure #}
|
||||
<div class="card mb-4 shadow-sm no-hover">
|
||||
<div class="card-body">
|
||||
<form method="get" class="row g-3 align-items-end">
|
||||
<!-- 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>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="searchInput" class="form-label small text-muted">{% trans "Search by Template Name" %}</label>
|
||||
<div class="input-group input-group-lg">
|
||||
<span class="input-group-text"><i class="fas fa-search text-muted"></i></span>
|
||||
<input type="text" name="q" id="searchInput" class="form-control form-control-search"
|
||||
placeholder="{% trans 'Search templates by name...' %}"
|
||||
<!-- 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>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
{# Show Clear button if search is active #}
|
||||
{% if query %}
|
||||
<a href="{% url 'form_templates_list' %}" class="btn btn-outline-secondary btn-lg">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Clear Search" %}
|
||||
<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 %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -196,110 +114,108 @@
|
||||
{% include "includes/_list_view_switcher.html" with list_id="form-templates-list" %}
|
||||
|
||||
{# 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 %}
|
||||
<div class="col-lg-4 col-md-6">
|
||||
<div class="card template-card h-100 shadow-sm">
|
||||
<div class="card-body d-flex flex-column">
|
||||
<h5 class="card-title fw-bold" style="color: var(--kaauh-teal-dark);">{{ template.name }}</h5>
|
||||
<span class="text-muted small mb-3">
|
||||
<i class="fas fa-briefcase me-1"></i> {{ template.job|default:"N/A" }}
|
||||
</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 class="col-6">
|
||||
<div class="stat-value">{{ template.get_field_count }}</div>
|
||||
<div class="stat-label">{% trans "Fields" %}</div>
|
||||
</div>
|
||||
<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="font-bold text-lg">{{ template.name }}</h5>
|
||||
<span class="text-sm text-white/80">
|
||||
<i data-lucide="briefcase" class="w-4 h-4 inline mr-1"></i> {{ template.job|default:"N/A" }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-4 space-y-3">
|
||||
{# Stats #}
|
||||
<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>
|
||||
|
||||
{# Description #}
|
||||
<p class="card-text small text-muted flex-grow-1">
|
||||
{% if template.description %}
|
||||
{{ template.description|truncatewords:20 }}
|
||||
{% else %}
|
||||
<em class="text-muted">{% trans "No description provided" %}</em>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{# Action area #}
|
||||
<div class="mt-auto pt-2 border-top">
|
||||
<div class="d-flex gap-2 justify-content-end">
|
||||
|
||||
<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 href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Submissions' %}">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}"
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-delete-url="#"
|
||||
data-item-name="{{ template.name }}">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</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 class="card-footer bg-light text-muted small">
|
||||
<div class="d-flex justify-content-between">
|
||||
<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>
|
||||
|
||||
{# Description #}
|
||||
<p class="text-sm text-gray-600">
|
||||
{% if template.description %}
|
||||
{{ template.description|truncatewords:20 }}
|
||||
{% else %}
|
||||
<em class="text-gray-400">{% trans "No description provided" %}</em>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{# Actions #}
|
||||
<div class="flex gap-2 pt-3 border-t border-gray-100">
|
||||
<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>
|
||||
<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 data-lucide="edit-2" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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 data-lucide="file-text" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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"
|
||||
onclick="deleteTemplate('{{ template.name }}', '#')">
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-gray-50 text-gray-500 text-xs p-3 flex justify-between">
|
||||
<span><i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i> {% trans "Created:" %} {{ template.created_at|date:"M d, Y" }}</span>
|
||||
<span><i data-lucide="refresh-cw" class="w-3 h-3 inline mr-1"></i> {{ template.updated_at|timesince }} {% trans "ago" %}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# Table View #}
|
||||
<div class="table-view">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<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 "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>
|
||||
<th scope="col" style="width: 30%;">{% trans "Template Name" %}</th>
|
||||
<th scope="col" style="width: 15%;">{% trans "Job" %}</th>
|
||||
<th scope="col" style="width: 8%;">{% trans "Stages" %}</th>
|
||||
<th scope="col" style="width: 8%;">{% trans "Fields" %}</th>
|
||||
<th scope="col" style="width: 15%;">{% trans "Created" %}</th>
|
||||
<th scope="col" style="width: 15%;">{% 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-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% 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 "Job" %}</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" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% 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 "Created" %}</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" class="px-6 py-3 text-center text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white divide-y divide-gray-100">
|
||||
{% for template in templates %}
|
||||
<tr>
|
||||
<td class="fw-medium text-primary">{{ template.name }}</td>
|
||||
<td>{{ template.job|default:"N/A" }}</td>
|
||||
<td>{{ template.get_stage_count }}</td>
|
||||
<td>{{ template.get_field_count }}</td>
|
||||
<td>{{ template.created_at|date:"M d, Y" }}</td>
|
||||
<td>{{ template.updated_at|date:"M d, Y" }}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary" title="{% trans 'Preview' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<a href="#" class="text-temple-red font-semibold hover:text-[#7a1a29] transition-colors">{{ template.name }}</a>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">{{ template.job|default:"N/A" }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">{{ template.get_stage_count }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">{{ template.get_field_count }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">{{ template.created_at|date:"M d, Y" }}</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">{{ template.updated_at|date:"M d, Y" }}</td>
|
||||
<td class="px-6 py-4 text-center">
|
||||
<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 href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
<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 data-lucide="edit-2" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Submissions' %}">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
<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 data-lucide="file-text" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<button type="button" class="btn btn-outline-danger" title="{% trans 'Delete' %}"
|
||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
||||
data-delete-url="#"
|
||||
data-item-name="{{ template.name }}">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
<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' %}"
|
||||
onclick="deleteTemplate('{{ template.name }}', '#')">
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
@ -311,80 +227,98 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Pagination (Standardized) #}
|
||||
{# Pagination #}
|
||||
{% if templates.has_other_pages %}
|
||||
<nav aria-label="Page navigation" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if templates.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1{% if query %}&q={{ query }}{% endif %}">First</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ templates.previous_page_number }}{% if query %}&q={{ query }}{% endif %}">Previous</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<nav aria-label="Page navigation" class="flex justify-center items-center gap-2">
|
||||
{% if templates.has_previous %}
|
||||
<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">
|
||||
{% trans "First" %}
|
||||
</a>
|
||||
<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">
|
||||
{% trans "Previous" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ templates.number }} of {{ templates.paginator.num_pages }}</span>
|
||||
</li>
|
||||
<span class="px-3 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||
{{ templates.number }} {% trans "of" %} {{ templates.paginator.num_pages }}
|
||||
</span>
|
||||
|
||||
{% if templates.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ templates.next_page_number }}{% if query %}&q={{ query }}{% endif %}">Next</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ templates.paginator.num_pages }}{% if query %}&q={{ query }}{% endif %}">Last</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% if templates.has_next %}
|
||||
<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">
|
||||
{% trans "Next" %}
|
||||
</a>
|
||||
<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">
|
||||
{% trans "Last" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center py-5 card shadow-sm">
|
||||
<div class="card-body">
|
||||
<i class="fas fa-file-contract fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
|
||||
<h3 class="h4 mb-3">{% trans "No Form Templates Found" %}</h3>
|
||||
<p class="text-muted">
|
||||
{% if query %}
|
||||
{% blocktrans with query=query %}No templates match your search "{{ query }}".{% endblocktrans %}
|
||||
{% else %}
|
||||
{% trans "You haven't created any form templates yet." %}
|
||||
{% endif %}
|
||||
</p>
|
||||
<button type="button" class="btn btn-main-action mt-3" data-bs-toggle="modal" data-bs-target="#createTemplateModal">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Create Your First Template" %}
|
||||
</button>
|
||||
</div>
|
||||
<!-- No Results -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||
<i data-lucide="file-text" 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 Form Templates Found" %}</h3>
|
||||
<p class="text-gray-500 mb-6">
|
||||
{% if query %}
|
||||
{% blocktrans with query=query %}No templates match your search "{{ query }}".{% endblocktrans %}
|
||||
{% else %}
|
||||
{% trans "You haven't created any form templates yet." %}
|
||||
{% endif %}
|
||||
</p>
|
||||
<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"
|
||||
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>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% include 'includes/delete_modal.html' %}
|
||||
|
||||
<div class="modal fade" id="createTemplateModal" tabindex="-1" aria-labelledby="createTemplateModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header bg-light">
|
||||
<h5 class="modal-title" id="createTemplateModalLabel">
|
||||
<i class="fas fa-file-alt me-2"></i>{% trans "Create New Form Template" %}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<!-- Create Template Modal -->
|
||||
<div id="createTemplateModal" class="fixed inset-0 z-50 hidden">
|
||||
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeCreateModal()"></div>
|
||||
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||
<div class="relative bg-white rounded-2xl shadow-2xl w-full max-w-lg">
|
||||
<div class="bg-gray-50 px-6 py-4 border-b border-gray-200 rounded-t-2xl">
|
||||
<h3 class="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||
<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 class="modal-body">
|
||||
{% url 'create_form_template' as create_form_template_url %}
|
||||
<div class="p-6">
|
||||
<form id="createTemplateForm" method="post" action="{% url 'create_form_template' %}">
|
||||
{% csrf_token %}
|
||||
{{form|crispy}}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
||||
<button type="submit" form="createTemplateForm" class="btn btn-main-action">
|
||||
<i class="fas fa-save me-1"></i>{% trans "Create Template" %}
|
||||
<div class="px-6 py-4 border-t border-gray-200 flex gap-3 justify-end">
|
||||
<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">
|
||||
{% trans "Cancel" %}
|
||||
</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>
|
||||
</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 %}
|
||||
@ -1,52 +1,52 @@
|
||||
{% load i18n %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div class="btn-group" role="group">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm view-toggle active" data-view="table" data-list-id="{{ list_id }}">
|
||||
<i class="fas fa-table me-1"></i> {% trans "Table" %}
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<div class="inline-flex rounded-lg overflow-hidden" role="group">
|
||||
<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 data-lucide="table" class="w-4 h-4"></i> {% trans "Table" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm view-toggle" data-view="card" data-list-id="{{ list_id }}">
|
||||
<i class="fas fa-th me-1"></i> {% trans "Card" %}
|
||||
<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 data-lucide="layout-grid" class="w-4 h-4"></i> {% trans "Card" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* --- View Switcher Styles (Consolidated & Corrected) --- */
|
||||
|
||||
/* View Toggle Styles */
|
||||
.view-toggle {
|
||||
border-radius: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
border-color: #d1d5db;
|
||||
color: #4b5563;
|
||||
background-color: white;
|
||||
}
|
||||
.view-toggle:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
.view-toggle.active {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
background-color: #b91c1c;
|
||||
border-color: #b91c1c;
|
||||
color: white;
|
||||
}
|
||||
.view-toggle.active:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
background-color: #991b1b;
|
||||
}
|
||||
|
||||
/* Hide elements by default */
|
||||
.table-view,
|
||||
.card-view {
|
||||
display: none !important; /* Use !important to ensure hiding works */
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Show active view */
|
||||
.table-view.active {
|
||||
display: block !important;
|
||||
display: block;
|
||||
}
|
||||
.card-view.active {
|
||||
/* Rely on the 'row' class which uses display: flex for proper column alignment. */
|
||||
display: flex !important; /* rows often use display: flex in Bootstrap */
|
||||
flex-wrap: wrap !important;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* Card View Styles */
|
||||
.card-view .card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
background-color: white;
|
||||
@ -56,7 +56,7 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
.card-view .card-header {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
background-color: #b91c1c;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
padding: 1rem 1.25rem;
|
||||
@ -67,24 +67,20 @@
|
||||
flex-grow: 1;
|
||||
}
|
||||
.card-view .card-title {
|
||||
color: var(--kaauh-teal-dark);
|
||||
color: #b91c1c;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.card-view .card-text {
|
||||
color: var(--kaauh-primary-text);
|
||||
color: #374151;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.card-view .card-footer {
|
||||
padding: 0.75rem 1.25rem;
|
||||
background-color: #f8f9fa;
|
||||
border-top: 1px solid var(--kaauh-border);
|
||||
border-top: 1px solid #e5e7eb;
|
||||
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 .table-responsive {
|
||||
@ -93,48 +89,43 @@
|
||||
}
|
||||
.table-view .table {
|
||||
margin-bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.table-view .table thead th {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
background-color: #b91c1c;
|
||||
color: white;
|
||||
border-color: var(--kaauh-border);
|
||||
border-color: #e5e7eb;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8rem;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
.table-view .table tbody td {
|
||||
padding: 1rem;
|
||||
vertical-align: middle;
|
||||
border-color: var(--kaauh-border);
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
.table-view .table tbody tr {
|
||||
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 {
|
||||
background-color: #f0f0f0; /* Fallback color for hover */
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
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;
|
||||
|
||||
// Get saved view preference from localStorage
|
||||
const savedView = localStorage.getItem(`list_view_${listId}`) || 'table';
|
||||
|
||||
// Set initial view
|
||||
setView(savedView);
|
||||
|
||||
// Add click event listeners to view toggle buttons
|
||||
document.querySelectorAll('.view-toggle').forEach(button => {
|
||||
// Ensure the button belongs to this list (optional check)
|
||||
if (button.getAttribute('data-list-id') === listId) {
|
||||
button.addEventListener('click', function() {
|
||||
const view = this.getAttribute('data-view');
|
||||
@ -144,7 +135,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
});
|
||||
|
||||
function setView(view) {
|
||||
// Update button states
|
||||
document.querySelectorAll('.view-toggle[data-list-id="{{ list_id }}"]').forEach(button => {
|
||||
if (button.getAttribute('data-view') === view) {
|
||||
button.classList.add('active');
|
||||
@ -153,11 +143,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Update view visibility
|
||||
const tableView = listContainer.querySelector('.table-view');
|
||||
const cardView = listContainer.querySelector('.card-view');
|
||||
|
||||
// Apply active class to the correct container
|
||||
if (view === 'table') {
|
||||
if (tableView) tableView.classList.add('active');
|
||||
if (cardView) cardView.classList.remove('active');
|
||||
@ -166,8 +154,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (cardView) cardView.classList.add('active');
|
||||
}
|
||||
|
||||
// Save preference to localStorage
|
||||
localStorage.setItem(`list_view_${listId}`, view);
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
@ -1,7 +1,35 @@
|
||||
{% load i18n %}
|
||||
{% 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 %}
|
||||
{{ form.as_p }}
|
||||
<button type="submit" class="btn btn-primary">{% trans "Update" %}</button>
|
||||
<div class="space-y-4">
|
||||
<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>
|
||||
@ -2,88 +2,91 @@
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
{% 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="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-briefcase me-2 text-primary"></i>
|
||||
<small class="text-muted">{% trans "Job Fit" %}</small>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="briefcase" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Job Fit" %}</small>
|
||||
</div>
|
||||
<p class="mb-1">{{ application.job_fit_narrative }}</p>
|
||||
<p class="text-gray-700 text-sm">{{ application.job_fit_narrative }}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-star me-2 text-warning"></i>
|
||||
<small class="text-muted">{% trans "Top Keywords" %}</small>
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Top Keywords" %}</small>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-clock me-2 text-info"></i>
|
||||
<small class="text-muted">{% trans "Experience" %}</small>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="clock" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Experience" %}</small>
|
||||
</div>
|
||||
<p class="mb-1"><strong>{{ 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 mb-1"><strong class="text-gray-900">{{ application.years_of_experience }}</strong> {% trans "years" %}</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 class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-chart-line me-2 text-success"></i>
|
||||
<small class="text-muted">{% trans "Skills" %}</small>
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="trending-up" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Skills" %}</small>
|
||||
</div>
|
||||
<p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
|
||||
<p class="mb-0"><strong>{% 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 %}">
|
||||
<p class="text-sm mb-1"><strong class="text-gray-900">{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
|
||||
<p class="text-sm"><strong class="text-gray-900">{% trans "Industry Match:" %}</strong>
|
||||
<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 }}%
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label>
|
||||
<textarea class="form-control" rows="6" readonly>{{ application.recommendation }}</textarea>
|
||||
<div class="mb-4">
|
||||
<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="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 class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label>
|
||||
<textarea class="form-control" rows="4" readonly>{{ application.strengths }}</textarea>
|
||||
<div class="mb-4">
|
||||
<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="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 class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label>
|
||||
<textarea class="form-control" rows="4" readonly>{{ application.weaknesses }}</textarea>
|
||||
<div class="mb-4">
|
||||
<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="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 class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-list-check me-1"></i> {% trans "Criteria Assessment" %}</label>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<div class="mb-4">
|
||||
<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="overflow-hidden rounded-lg border border-gray-200">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Criteria" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<tr class="bg-temple-red text-white">
|
||||
<th class="px-4 py-3 text-left font-semibold">{% trans "Criteria" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold">{% trans "Status" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
{% for criterion, status in application.criteria_checklist.items %}
|
||||
<tr>
|
||||
<td>{{ criterion }}</td>
|
||||
<td>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-gray-900">{{ criterion }}</td>
|
||||
<td class="px-4 py-3">
|
||||
{% 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" %}
|
||||
<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 %}
|
||||
<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 %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -93,121 +96,124 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-check-circle me-2 text-success"></i>
|
||||
<small class="text-muted">{% trans "Minimum Requirements" %}</small>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="check-circle" class="w-4 h-4 text-green-600"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Minimum Requirements" %}</small>
|
||||
</div>
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-star me-2 text-warning"></i>
|
||||
<small class="text-muted">{% trans "Screening Rating" %}</small>
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Screening Rating" %}</small>
|
||||
</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>
|
||||
|
||||
{% if application.language_fluency %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<div class="mb-4">
|
||||
<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="flex flex-wrap gap-2">
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% 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="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-briefcase me-2 text-primary"></i>
|
||||
<small class="text-muted">{% trans "Job Fit" %}</small>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="briefcase" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Job Fit" %}</small>
|
||||
</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 class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-star me-2 text-warning"></i>
|
||||
<small class="text-muted">{% trans "Top Keywords" %}</small>
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Top Keywords" %}</small>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-clock me-2 text-info"></i>
|
||||
<small class="text-muted">{% trans "Experience" %}</small>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="clock" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Experience" %}</small>
|
||||
</div>
|
||||
<p class="mb-1"><strong>{{ 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 mb-1"><strong class="text-gray-900">{{ application.years_of_experience }}</strong> {% trans "years" %}</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 class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-chart-line me-2 text-success"></i>
|
||||
<small class="text-muted">{% trans "Skills" %}</small>
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="trending-up" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Skills" %}</small>
|
||||
</div>
|
||||
<p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
|
||||
<p class="mb-0"><strong>{% 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 %}">
|
||||
<p class="text-sm mb-1"><strong class="text-gray-900">{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
|
||||
<p class="text-sm"><strong class="text-gray-900">{% trans "Industry Match:" %}</strong>
|
||||
<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 }}%
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label>
|
||||
<textarea class="form-control" rows="6" readonly>{{ application.recommendation_ar }}</textarea>
|
||||
<div class="mb-4">
|
||||
<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="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 class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label>
|
||||
<textarea class="form-control" rows="4" readonly>{{ application.strengths_ar }}</textarea>
|
||||
<div class="mb-4">
|
||||
<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="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 class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label>
|
||||
<textarea class="form-control" rows="4" readonly>{{ application.weaknesses_ar }}</textarea>
|
||||
<div class="mb-4">
|
||||
<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="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 class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-list-check me-1"></i> {% trans "Criteria Assessment" %}</label>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm">
|
||||
<div class="mb-4">
|
||||
<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="overflow-hidden rounded-lg border border-gray-200">
|
||||
<table class="w-full text-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Criteria" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<tr class="bg-temple-red text-white">
|
||||
<th class="px-4 py-3 text-left font-semibold">{% trans "Criteria" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold">{% trans "Status" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody class="bg-white divide-y divide-gray-200">
|
||||
{% for criterion, status in application.criteria_checklist_ar.items %}
|
||||
<tr>
|
||||
<td>{{ criterion }}</td>
|
||||
<td>
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-gray-900">{{ criterion }}</td>
|
||||
<td class="px-4 py-3">
|
||||
{% 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" %}
|
||||
<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 %}
|
||||
<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 %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -217,36 +223,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-check-circle me-2 text-success"></i>
|
||||
<small class="text-muted">{% trans "Minimum Requirements" %}</small>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="check-circle" class="w-4 h-4 text-green-600"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Minimum Requirements" %}</small>
|
||||
</div>
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<i class="fas fa-star me-2 text-warning"></i>
|
||||
<small class="text-muted">{% trans "Screening Rating" %}</small>
|
||||
<div class="bg-gray-50 rounded-xl p-4">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
|
||||
<small class="text-gray-600 font-medium">{% trans "Screening Rating" %}</small>
|
||||
</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>
|
||||
|
||||
{% if application.language_fluency_ar %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<div class="mb-4">
|
||||
<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="flex flex-wrap gap-2">
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
@ -1,34 +1,35 @@
|
||||
{% 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 }}"
|
||||
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="form-check d-flex align-items-center gap-2">
|
||||
<input class="form-check-input" type="radio" name="exam_status" id="exam_passed" value="Passed" {% if application.exam_status == 'Passed' %}checked{% endif %}>
|
||||
<label class="form-check-label" for="exam_passed">
|
||||
<i class="fas fa-check me-1"></i> {% trans "Passed" %}
|
||||
</label>
|
||||
<div class="flex justify-center items-center gap-4 mb-4">
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="exam_status" id="exam_passed" value="Passed" {% if application.exam_status == 'Passed' %}checked{% endif %} class="w-4 h-4 text-temple-red border-gray-300 focus:ring-temple-red/20">
|
||||
<span class="text-gray-700 text-sm"><i data-lucide="check" class="w-4 h-4 inline text-green-600"></i> {% trans "Passed" %}</span>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center items-center mt-4 gap-4">
|
||||
<div class="w-24 text-right">
|
||||
<label for="exam_score" class="block text-sm font-medium text-gray-600">{% trans "Exam Score" %}</label>
|
||||
</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 %}>
|
||||
<label class="form-check-label" for="exam_failed">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Failed" %}
|
||||
</label>
|
||||
<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 class="d-flex justify-content-center align-items-center mt-3 gap-2">
|
||||
<div class="w-25 text-end pe-none">
|
||||
<label for="exam_score" class="form-label small text-muted">{% trans "Exam Score" %}</label>
|
||||
</div>
|
||||
<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" %}
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<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">
|
||||
<i data-lucide="check" class="w-4 h-4"></i> {% trans "Update" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
@ -1,10 +1,14 @@
|
||||
{% 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(); }">
|
||||
<a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Passed' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-check me-1"></i> {% trans "Passed" %}
|
||||
<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 data-lucide="check" class="w-4 h-4"></i> {% trans "Passed" %}
|
||||
</a>
|
||||
<a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Failed' %}" class="btn btn-danger">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Failed" %}
|
||||
<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 data-lucide="x" class="w-4 h-4"></i> {% trans "Failed" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
@ -1,10 +1,14 @@
|
||||
{% 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(); }">
|
||||
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-check me-1"></i> {% trans "Accepted" %}
|
||||
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-300 text-gray-700 hover:border-temple-red hover:text-temple-red rounded-lg text-sm font-medium transition">
|
||||
<i data-lucide="check" class="w-4 h-4"></i> {% trans "Accepted" %}
|
||||
</a>
|
||||
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Rejected' %}" class="btn btn-danger">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Rejected" %}
|
||||
<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 data-lucide="x" class="w-4 h-4"></i> {% trans "Rejected" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
@ -1,541 +1,209 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Resume - Abdullah Sami Bakhsh</title>
|
||||
<style>
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-light-bg: #f9fbfd;
|
||||
--kaauh-border: #eaeff3;
|
||||
--text-primary: #2c3e50;
|
||||
--text-secondary: #6c757d;
|
||||
--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 class="contact-item">
|
||||
<span>✉️</span>
|
||||
<span>Bakhsh.Abdullah@Outlook.com</span>
|
||||
</div>
|
||||
<div class="contact-item">
|
||||
<span>📍</span>
|
||||
<span>Saudi Arabia</span>
|
||||
</div>
|
||||
{% load i18n %}
|
||||
<div class="max-w-7xl mx-auto bg-white rounded-3xl overflow-hidden shadow-2xl">
|
||||
<!-- Header -->
|
||||
<div class="bg-gradient-to-br from-temple-red to-[#004a53] text-white p-10 relative overflow-hidden">
|
||||
<div class="relative z-10">
|
||||
<h1 class="text-5xl font-bold mb-2.5">
|
||||
{% if application.name %}{{ application.name }}{% else %}{% trans "Candidate" %}{% endif %}
|
||||
</h1>
|
||||
<div class="text-xl opacity-90 mb-5">{% trans "Resume Analysis" %}</div>
|
||||
<div class="flex flex-wrap gap-5 text-base">
|
||||
{% if application.email %}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">📱</span>
|
||||
<span>{{ application.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div class="left-column">
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
<span>📋</span> Professional Summary
|
||||
</h2>
|
||||
<p class="summary">
|
||||
Strategic and results‑driven HR leader with over 11 years of experience in human resources, specializing in business partnering, workforce planning, talent acquisition, training & development, and organizational effectiveness.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if application.phone %}
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-lg">✉️</span>
|
||||
<span>{{ application.phone }}</span>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
<span>💼</span> Work Experience
|
||||
</h2>
|
||||
<div class="experience-item">
|
||||
<div class="job-header">
|
||||
<div>
|
||||
<div class="job-title">Head, Recruitment & Training</div>
|
||||
<div class="company">TASNEE - DOWNSTREAM & METALLURGY SBUs</div>
|
||||
</div>
|
||||
<div class="job-period">Oct 2024 - Present</div>
|
||||
</div>
|
||||
<ul class="achievements">
|
||||
<li>Led recruitment and training initiatives across downstream and metallurgical divisions</li>
|
||||
<li>Developed workforce analytics program</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="experience-item">
|
||||
<div class="job-header">
|
||||
<div>
|
||||
<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 class="experience-item">
|
||||
<div class="job-header">
|
||||
<div>
|
||||
<div class="job-title">Specialist, Recruitment</div>
|
||||
<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>
|
||||
<div class="education-item">
|
||||
<div class="degree">Associate Diploma in People Management Level 5</div>
|
||||
<div class="institution">Chartered Institute of Personnel and Development (CIPD)</div>
|
||||
</div>
|
||||
<div class="education-item">
|
||||
<div class="degree">Bachelor's Degree in Chemical Engineering Technology</div>
|
||||
<div class="institution">Yanbu Industrial College</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-column">
|
||||
<div class="score-card">
|
||||
<div class="match-score">10%</div>
|
||||
<div class="score-label">Match Score</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
<span>🔍</span> Assessment
|
||||
</h2>
|
||||
<div class="strengths-weaknesses">
|
||||
<div class="strength-box">
|
||||
<div class="box-title">Strengths</div>
|
||||
<div class="box-content">Extensive HR leadership and project management experience</div>
|
||||
</div>
|
||||
<div class="weakness-box">
|
||||
<div class="box-title">Weaknesses</div>
|
||||
<div class="box-content">Lack of IT infrastructure, cybersecurity, and relevant certifications</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recommendation">
|
||||
<div class="recommendation-title">Recommendation</div>
|
||||
<div class="recommendation-text">Candidate does not meet the IT management requirements; not recommended for interview.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
<span>💡</span> Top Keywords
|
||||
</h2>
|
||||
<div class="skills-container">
|
||||
<span class="keyword-tag">HR</span>
|
||||
<span class="keyword-tag">Recruitment</span>
|
||||
<span class="keyword-tag">Training</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<h2 class="section-title">
|
||||
<span>🛠️</span> Skills
|
||||
</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">
|
||||
<h2 class="section-title">
|
||||
<span>🌍</span> Languages
|
||||
</h2>
|
||||
<div class="language-item">
|
||||
<span class="language-name">Arabic</span>
|
||||
<span class="proficiency">Native</span>
|
||||
</div>
|
||||
<div class="language-item">
|
||||
<span class="language-name">English</span>
|
||||
<span class="proficiency">Fluent</span>
|
||||
</div>
|
||||
<div class="language-item">
|
||||
<span class="language-name">Japanese</span>
|
||||
<span class="proficiency">Intermediate</span>
|
||||
</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>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-[1fr_350px] gap-8 p-10">
|
||||
|
||||
<!-- Left Column -->
|
||||
<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>
|
||||
<p class="text-gray-600 leading-8">
|
||||
{% 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>
|
||||
</div>
|
||||
|
||||
<!-- Work Experience -->
|
||||
<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 "Work Experience" %}
|
||||
</h2>
|
||||
|
||||
{% 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 class="font-bold text-gray-900 text-lg">{{ exp.title }}</div>
|
||||
<div class="text-gray-600 font-medium">{{ exp.company }}</div>
|
||||
</div>
|
||||
<div class="text-gray-500 text-sm whitespace-nowrap">{{ exp.period }}</div>
|
||||
</div>
|
||||
<ul class="list-disc list-inside pl-5 text-gray-600 space-y-1.5">
|
||||
{% for responsibility in exp.responsibilities %}
|
||||
<li>{{ responsibility }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-gray-400 italic">{% trans "No work experience data available" %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Education -->
|
||||
<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 "Education" %}
|
||||
</h2>
|
||||
|
||||
{% if application.ai_analysis_data.education %}
|
||||
{% for edu in application.ai_analysis_data.education %}
|
||||
<div class="mb-4 pb-4 border-b border-gray-200 last:border-0 last:mb-0 last:pb-0">
|
||||
<div class="font-bold text-gray-900">{{ edu.degree }}</div>
|
||||
<div class="text-gray-600">{{ edu.institution }}</div>
|
||||
<div class="text-gray-500 text-sm mt-1">{{ edu.year }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-gray-400 italic">{% trans "No education data available" %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column -->
|
||||
<div class="flex flex-col gap-6">
|
||||
<!-- Match Score -->
|
||||
{% 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>
|
||||
{% endif %}
|
||||
|
||||
<!-- Assessment -->
|
||||
{% if application.ai_analysis_data %}
|
||||
<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>
|
||||
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
|
||||
<div class="bg-emerald-50 border border-emerald-500 p-4 rounded-xl">
|
||||
<div class="font-bold text-emerald-700 mb-2 text-sm flex items-center gap-2">
|
||||
<span>✅</span> {% trans "Strengths" %}
|
||||
</div>
|
||||
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.strengths }}</div>
|
||||
</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 class="bg-temple-red/5 border-l-4 border-l-temple-red p-4 rounded-xl">
|
||||
<div class="font-bold text-temple-red mb-2 flex items-center gap-2">
|
||||
<span>💡</span> {% trans "Recommendation" %}
|
||||
</div>
|
||||
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.recommendation }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Top Keywords -->
|
||||
{% if application.ai_analysis_data %}
|
||||
<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>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% for keyword in application.ai_analysis_data.top_3_keywords %}
|
||||
<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">
|
||||
{{ 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>
|
||||
|
||||
<!-- Professional Details -->
|
||||
{% if application.ai_analysis_data %}
|
||||
<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>
|
||||
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-gray-600 text-sm">{% trans "Years of Experience:" %}</span>
|
||||
<strong class="text-gray-900">{{ application.ai_analysis_data.years_of_experience }}</strong>
|
||||
</div>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-gray-600 text-sm">{% trans "Recent Job Title:" %}</span>
|
||||
<strong class="text-gray-900 text-sm text-right">{{ application.ai_analysis_data.most_recent_job_title }}</strong>
|
||||
</div>
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-gray-600 text-sm">{% trans "Industry Match:" %}</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>
|
||||
|
||||
@ -1,23 +1,26 @@
|
||||
<div class="card mt-4">
|
||||
<div class="card-header text-primary-theme">
|
||||
<h5 class="card-title mb-0">{% trans "Add Comment" %}</h5>
|
||||
{% load i18n %}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
|
||||
<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 class="card-body">
|
||||
<div class="p-4">
|
||||
<form method="post" action="{% url 'add_meeting_comment' meeting.slug %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
{{ form.content }}
|
||||
{% 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 %}
|
||||
<small>{{ error }}</small>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</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 %}
|
||||
<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 %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -1,56 +1,59 @@
|
||||
{% load i18n %}
|
||||
<div id="comment-section">
|
||||
<div class="card mt-4">
|
||||
<div class="card-header text-primary-theme d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">{% trans "Comments" %} ({{ comments.count }})</h5>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
|
||||
<div class="bg-gray-50 px-4 py-3 border-b border-gray-200 flex items-center justify-between">
|
||||
<h5 class="text-lg font-bold text-temple-red mb-0">{% trans "Comments" %} ({{ comments.count }})</h5>
|
||||
{% 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">
|
||||
<i class="bi bi-x-lg"></i> {% trans "Close" %}
|
||||
<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 data-lucide="x" class="w-4 h-4"></i> {% trans "Close" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="p-4">
|
||||
{% if comments %}
|
||||
<div class="row">
|
||||
<div class="space-y-3">
|
||||
{% for comment in comments %}
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-start ">
|
||||
<div>
|
||||
<strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong>
|
||||
{% if comment.author != user %}
|
||||
<span class="badge bg-secondary ms-2">{% trans "Comment" %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<small class="text-muted">{{ comment.created_at|date:"M d, Y P" }}</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="card-text">{{ comment.content|safe }}</p>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
{% if comment.author == user or user.is_staff %}
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-outline-primary"
|
||||
hx-get="{% url 'edit_meeting_comment' meeting.slug comment.id %}"
|
||||
hx-target="#comment-section"
|
||||
title="Edit Comment">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
hx-get="{% url 'delete_meeting_comment' meeting.slug comment.id %}"
|
||||
hx-target="#comment-section"
|
||||
title="Delete Comment">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-gray-50 rounded-xl overflow-hidden">
|
||||
<div class="px-4 py-3 bg-white border-b border-gray-100 flex items-center justify-between">
|
||||
<div>
|
||||
<strong class="text-gray-900">{{ comment.author.get_full_name|default:comment.author.username }}</strong>
|
||||
{% if comment.author != user %}
|
||||
<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 %}
|
||||
</div>
|
||||
<small class="text-gray-500 text-sm">{{ comment.created_at|date:"M d, Y P" }}</small>
|
||||
</div>
|
||||
<div class="px-4 py-3">
|
||||
<p class="text-gray-700 mb-0">{{ comment.content|safe }}</p>
|
||||
</div>
|
||||
<div class="px-4 py-2 bg-gray-50 border-t border-gray-100">
|
||||
{% if comment.author == user or user.is_staff %}
|
||||
<div class="flex items-center gap-2">
|
||||
<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-target="#comment-section"
|
||||
title="Edit Comment">
|
||||
<i data-lucide="pencil" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<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-target="#comment-section"
|
||||
title="Delete Comment">
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
@ -1,20 +1,58 @@
|
||||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
||||
<div id="copyToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
||||
<div class="toast-header">
|
||||
<i class="fas fa-check-circle text-success me-2"></i>
|
||||
<strong class="me-auto">{% trans "Success" %}</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
{% load i18n %}
|
||||
|
||||
<!-- Toast Notification -->
|
||||
<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">
|
||||
<div class="bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden pointer-events-auto min-w-[320px]">
|
||||
<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>
|
||||
<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="toast-body">
|
||||
<div class="px-4 py-3 text-sm text-gray-700">
|
||||
{% blocktrans with text=text %}Copied "{{ text }}" to clipboard!{% endblocktrans %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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() {
|
||||
const toast = new bootstrap.Toast(document.getElementById('copyToast'));
|
||||
toast.show();
|
||||
showToast();
|
||||
});
|
||||
|
||||
// Initialize icons
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
@ -1,15 +1,18 @@
|
||||
<div class="card mt-4">
|
||||
<div class="card-header text-primary-theme">
|
||||
<h5 class="card-title mb-0">{% trans "Delete Comment" %}</h5>
|
||||
{% load i18n %}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
|
||||
<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 class="card-body">
|
||||
<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><small>{{ comment.created_at|date:"F d, Y P" }}</small></p>
|
||||
<div class="p-4">
|
||||
<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 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 }}">
|
||||
{% 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 %}
|
||||
<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 %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -1,18 +1,22 @@
|
||||
{% load i18n %}
|
||||
<!-- Delete Confirmation Modal -->
|
||||
<div class="modal fade" id="meetingModal" tabindex="-1" aria-labelledby="meetingModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="meetingModalLabel">
|
||||
<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="bg-white rounded-xl shadow-xl max-w-md 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" id="meetingModalLabel">
|
||||
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div id="meetingModalBody" class="modal-body px-4 py-3">
|
||||
|
||||
</div>
|
||||
</h5>
|
||||
<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 id="meetingModalBody" class="p-4">
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
@ -4,35 +4,18 @@
|
||||
|
||||
<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">
|
||||
<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>
|
||||
{% trans "Documents" %}
|
||||
</h5>
|
||||
<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"
|
||||
onclick="document.getElementById('documentUploadModal').classList.remove('hidden')">
|
||||
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"
|
||||
data-modal="documentUploadModal">
|
||||
<i data-lucide="upload-cloud" class="w-4 h-4"></i>
|
||||
{% trans "Upload Document" %}
|
||||
</button>
|
||||
</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 -->
|
||||
<div class="p-4" id="document-list-container">
|
||||
{% 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 class="flex items-start gap-3 flex-1">
|
||||
<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 class="flex-1 min-w-0">
|
||||
<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">
|
||||
<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" %}'>
|
||||
<i data-lucide="download" class="w-4 h-4"></i>
|
||||
</a>
|
||||
@ -87,15 +70,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
|
||||
// Close modal on outside click
|
||||
document.addEventListener('click', function(e) {
|
||||
const modal = document.getElementById('documentUploadModal');
|
||||
if (e.target === modal) {
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<!-- Document Upload Modal (moved outside the card structure to avoid z-index issues) -->
|
||||
<div id="documentUploadModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-[60] 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"
|
||||
class="modal-close-btn text-gray-400 hover:text-gray-600 transition"
|
||||
data-modal="documentUploadModal">
|
||||
<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>
|
||||
|
||||
@ -4,345 +4,191 @@
|
||||
|
||||
{% 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 %}
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="flex items-center gap-2 text-sm">
|
||||
<li>
|
||||
<a href="{% url 'settings' %}" class="text-gray-600 hover:text-temple-red transition">{% trans "Settings" %}</a>
|
||||
</li>
|
||||
<li class="text-gray-400">/</li>
|
||||
<li class="font-semibold text-temple-red">{% trans "System Activity" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="container-fluid pt-5 pb-5 px-lg-5" style="background-color: var(--color-background-light);">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page" style="
|
||||
color: #F43B5E; /* Rosy Accent Color */
|
||||
font-weight: 600;
|
||||
">{% trans "System Activity" %}</li>
|
||||
</ol>
|
||||
</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" %}
|
||||
<!-- Header -->
|
||||
<div class="mb-4">
|
||||
<h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
||||
<i data-lucide="shield" class="w-6 h-6 text-temple-red"></i>
|
||||
{% trans "System Activity Logs" %}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="alert summary-alert border-start border-5 p-3 mb-5 mx-3" role="alert">
|
||||
<h6 class="mb-1">{% trans "Viewing Logs" %}: <strong>{{ tab_title }}</strong></h6>
|
||||
<p class="mb-0 small">
|
||||
{% trans "Displaying" %}: **{{ logs.start_index }}-{{ logs.end_index }}** {% trans "of" %}
|
||||
**{{ total_count }}** {% trans "total records." %}
|
||||
</p>
|
||||
<!-- 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>
|
||||
</div>
|
||||
|
||||
<!-- Audit Card -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 min-h-[60vh]">
|
||||
|
||||
<!-- Tabs -->
|
||||
<div class="flex border-b border-gray-200 px-3 pt-4 rounded-t-xl">
|
||||
<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 %}"
|
||||
href="?tab=crud">
|
||||
<i data-lucide="database" class="w-4 h-4 inline mr-2"></i>{% trans "Model Changes (CRUD)" %}
|
||||
</a>
|
||||
<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 %}"
|
||||
href="?tab=login">
|
||||
<i data-lucide="user-lock" class="w-4 h-4 inline mr-2"></i>{% trans "User Authentication" %}
|
||||
</a>
|
||||
<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 %}"
|
||||
href="?tab=request">
|
||||
<i data-lucide="globe" class="w-4 h-4 inline mr-2"></i>{% trans "HTTP Requests" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="audit-card mx-3">
|
||||
<!-- Tab Content -->
|
||||
<div class="p-4">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-sm">
|
||||
|
||||
<ul class="nav nav-tabs px-3" id="auditTabs" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link nav-link-es {% if active_tab == 'crud' %}active{% endif %}"
|
||||
id="crud-tab" href="?tab=crud" aria-controls="crud">
|
||||
<i class="fas fa-database me-2"></i>{% trans "Model Changes (CRUD)" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link nav-link-es {% if active_tab == 'login' %}active{% endif %}"
|
||||
id="login-tab" href="?tab=login" aria-controls="login">
|
||||
<i class="fas fa-user-lock me-2"></i>{% trans "User Authentication" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link nav-link-es {% if active_tab == 'request' %}active{% endif %}"
|
||||
id="request-tab" href="?tab=request" aria-controls="request">
|
||||
<i class="fas fa-globe me-2"></i>{% trans "HTTP Requests" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<thead>
|
||||
{% if active_tab == 'crud' %}
|
||||
<tr class="bg-gray-50">
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Action" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Model" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Object PK" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Changes" %}</th>
|
||||
</tr>
|
||||
{% elif active_tab == 'login' %}
|
||||
<tr class="bg-gray-50">
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Type" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Status" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "IP Address" %}</th>
|
||||
</tr>
|
||||
{% elif active_tab == 'request' %}
|
||||
<tr class="bg-gray-50">
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Method" %}</th>
|
||||
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Path" %}</th>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</thead>
|
||||
|
||||
<div class="tab-content p-4" id="auditTabsContent">
|
||||
<tbody class="divide-y divide-gray-100">
|
||||
{% for log in logs.object_list %}
|
||||
{% if active_tab == 'crud' %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td class="px-4 py-3 text-gray-900">{{ log.user.email|default:"N/A" }}</td>
|
||||
<td class="px-4 py-3">
|
||||
{% if log.event_type == 1 %}
|
||||
<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 %}
|
||||
<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>
|
||||
{% else %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</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 class="px-4 py-3 text-gray-900">{{ log.object_id }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<pre class="bg-gray-100 p-2 rounded text-xs overflow-x-auto max-h-20 overflow-y-auto">{{ log.changed_fields }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<div class="tab-pane fade show active" role="tabpanel">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover small">
|
||||
|
||||
<thead>
|
||||
{% if active_tab == 'crud' %}
|
||||
<tr>
|
||||
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th>
|
||||
<th scope="col" style="width: 15%;">{% trans "User" %}</th>
|
||||
<th scope="col" style="width: 10%;">{% trans "Action" %}</th>
|
||||
<th scope="col" style="width: 20%;">{% trans "Model" %}</th>
|
||||
<th scope="col" style="width: 10%;">{% trans "Object PK" %}</th>
|
||||
<th scope="col" style="width: 30%;">{% trans "Changes" %}</th>
|
||||
</tr>
|
||||
{% elif active_tab == 'login' %}
|
||||
<tr>
|
||||
<th scope="col" style="width: 20%;">{% trans "Date/Time" %}</th>
|
||||
<th scope="col" style="width: 25%;">{% trans "User" %}</th>
|
||||
<th scope="col" style="width: 15%;">{% trans "Type" %}</th>
|
||||
<th scope="col" style="width: 10%;">{% trans "Status" %}</th>
|
||||
<th scope="col" style="width: 30%;">{% trans "IP Address" %}</th>
|
||||
</tr>
|
||||
{% elif active_tab == 'request' %}
|
||||
<tr>
|
||||
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th>
|
||||
<th scope="col" style="width: 15%;">{% trans "User" %}</th>
|
||||
<th scope="col" style="width: 10%;">{% trans "Method" %}</th>
|
||||
<th scope="col" style="width: 45%;">{% trans "Path" %}</th>
|
||||
|
||||
</tr>
|
||||
{% endif %}
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for log in logs.object_list %}
|
||||
{% if active_tab == 'crud' %}
|
||||
<tr>
|
||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>{{ log.user.email|default:"N/A" }}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill
|
||||
{% if log.event_type == 1 %}badges-crud-create
|
||||
{% elif log.event_type == 2 %}badges-crud-update
|
||||
{% else %}badges-crud-delete{% endif %}">
|
||||
{% if log.event_type == 1 %}<i class="fas fa-plus-circle me-1"></i>{% trans "CREATE" %}
|
||||
{% elif log.event_type == 2 %}<i class="fas fa-edit me-1"></i>{% trans "UPDATE" %}
|
||||
{% else %}<i class="fas fa-trash-alt me-1"></i>{% trans "DELETE" %}{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td><code style="color: var(--color-text-dark) !important;">{{ log.content_type.app_label }}.{{ log.content_type.model }}</code></td>
|
||||
<td>{{ log.object_id }}</td>
|
||||
<td>
|
||||
<pre class="p-2 m-0" style="font-size: 0.65rem; max-height: 80px; overflow-y: auto;">{{ log.changed_fields }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% elif active_tab == 'login' %}
|
||||
<tr>
|
||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>
|
||||
{% with user_obj=log.user %}
|
||||
{% if user_obj %}
|
||||
{{ user_obj.get_full_name|default:user_obj.username }}
|
||||
{% else %}
|
||||
<span class="text-danger fw-bold">{{ log.username|default:"N/A" }}</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill badges-login-status">
|
||||
{% if log.login_type == 0 %}{% trans "Login" %}
|
||||
{% elif log.login_type == 1 %}{% trans "Logout" %}
|
||||
{% else %}{% trans "Failed Login" %}{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if log.login_type == 2 %}
|
||||
<i class="fas fa-times-circle me-1" style="color: var(--color-danger);"></i>{% trans "Failed" %}
|
||||
{% else %}
|
||||
<i class="fas fa-check-circle me-1" style="color: var(--color-success);"></i>{% trans "Success" %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ log.remote_ip|default:"Unknown" }}</td>
|
||||
</tr>
|
||||
|
||||
{% elif active_tab == 'request' %}
|
||||
<tr>
|
||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill badges-request-method">{{ log.method }}</span>
|
||||
</td>
|
||||
<td><code class="text-break small" style="color: var(--color-text-dark) !important;">{{ log.url}}</code></td>
|
||||
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<tr><td colspan="6" class="text-center text-muted py-5">
|
||||
<i class="fas fa-info-circle me-2"></i>{% trans "No logs found for this section or the database is empty." %}
|
||||
</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if logs.has_other_pages %}
|
||||
<nav aria-label="Audit Log Pagination" class="pt-3">
|
||||
<ul class="pagination justify-content-end">
|
||||
|
||||
<li class="page-item {% if not logs.has_previous %}disabled{% endif %}">
|
||||
<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">«</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% 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' %}
|
||||
<li class="page-item {% if logs.number == i %}active{% endif %}">
|
||||
<a class="page-link"
|
||||
href="?tab={{ active_tab }}&page={{ i }}">
|
||||
{{ i }}
|
||||
</a>
|
||||
</li>
|
||||
{% elif i == logs.number|add:'-3' or i == logs.number|add:'3' %}
|
||||
<li class="page-item disabled"><span class="page-link">...</span></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<li class="page-item {% if not logs.has_next %}disabled{% endif %}">
|
||||
<a class="page-link"
|
||||
href="?tab={{ active_tab }}{% if logs.has_next %}&page={{ logs.next_page_number }}{% endif %}"
|
||||
aria-label="Next">
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% elif active_tab == 'login' %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td class="px-4 py-3 text-gray-900">
|
||||
{% with user_obj=log.user %}
|
||||
{% if user_obj %}
|
||||
{{ user_obj.get_full_name|default:user_obj.username }}
|
||||
{% else %}
|
||||
<span class="text-red-600 font-semibold">{{ log.username|default:"N/A" }}</span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<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" %}
|
||||
{% elif log.login_type == 1 %}{% trans "Logout" %}
|
||||
{% else %}{% trans "Failed Login" %}{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
{% if log.login_type == 2 %}
|
||||
<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 %}
|
||||
<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 %}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-gray-900">{{ log.remote_ip|default:"Unknown" }}</td>
|
||||
</tr>
|
||||
|
||||
{% elif active_tab == 'request' %}
|
||||
<tr class="hover:bg-gray-50">
|
||||
<td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td class="px-4 py-3 text-gray-900">{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td>
|
||||
<td class="px-4 py-3">
|
||||
<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 class="px-4 py-3"><code class="bg-gray-100 px-2 py-1 rounded text-xs break-all">{{ log.url}}</code></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-12 text-center text-gray-500">
|
||||
<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 %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if logs.has_other_pages %}
|
||||
<div class="flex justify-end items-center gap-2 pt-4">
|
||||
<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">
|
||||
<i data-lucide="chevron-left" class="w-4 h-4 inline"></i>
|
||||
</a>
|
||||
|
||||
{% for i in logs.paginator.page_range %}
|
||||
{% if i > logs.number|add:'-3' and i < logs.number|add:'3' %}
|
||||
<a href="?tab={{ active_tab }}&page={{ i }}"
|
||||
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">
|
||||
{{ i }}
|
||||
</a>
|
||||
{% elif i == logs.number|add:'-3' or i == logs.number|add:'3' %}
|
||||
<span class="px-3 py-2 text-sm text-gray-400">...</span>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
<a href="?tab={{ active_tab }}{% if logs.has_next %}&page={{ logs.next_page_number }}{% endif %}"
|
||||
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">
|
||||
<i data-lucide="chevron-right" class="w-4 h-4 inline"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,23 +1,26 @@
|
||||
<div class="card mt-4">
|
||||
<div class="card-header text-primary-theme">
|
||||
<h5 class="card-title mb-0">{% trans "Edit Comment" %}</h5>
|
||||
{% load i18n %}
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
|
||||
<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 class="card-body">
|
||||
<div class="p-4">
|
||||
<form method="post" action="{% url 'edit_meeting_comment' meeting.slug comment.id %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
{{ form.content }}
|
||||
{% 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 %}
|
||||
<small>{{ error }}</small>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</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 %}
|
||||
<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 %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -1,217 +1,181 @@
|
||||
{% load i18n %}
|
||||
{{ form.media }}
|
||||
<div class="row">
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="col-12">
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% for message in messages %}
|
||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
|
||||
{{ message }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
<div class="card">
|
||||
|
||||
<div class="card-body">
|
||||
<form hx-boost="true" method="post" id="email-compose-form" action="{% url 'compose_application_email' job.slug %}"
|
||||
hx-include="#application-form"
|
||||
|
||||
hx-push-url="false"
|
||||
hx-swap="outerHTML"
|
||||
hx-on::after-request="new bootstrap.Modal('#emailModal')).hide()">
|
||||
{% csrf_token %}
|
||||
<!-- Recipients Field -->
|
||||
<!-- Recipients Field -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-bold">
|
||||
{% trans "To" %}
|
||||
</label>
|
||||
<div class="border rounded p-3 bg-light" style="max-height: 200px; overflow-y: auto;">
|
||||
|
||||
{# --- 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 %}
|
||||
<input type="hidden" name="{{ form.to.name }}" value="{{ choice.data.value }}">
|
||||
{% endfor %}
|
||||
|
||||
{# --- 2. VISUAL LAYER: Show only the first one --- #}
|
||||
{# We make it disabled so the user knows they can't deselect it here #}
|
||||
{% for choice in form.to|slice:":1" %}
|
||||
<div class="form-check mb-2">
|
||||
<input class="form-check-input" type="checkbox" checked disabled>
|
||||
<label class="form-check-label">
|
||||
{{ choice.choice_label }}
|
||||
</label>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{# --- 3. SUMMARY: Show count of hidden recipients --- #}
|
||||
{% if form.to|length > 1 %}
|
||||
<div class="text-muted small mt-2">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{# Use simple math to show remaining count #}
|
||||
{% with remaining=form.to|length|add:"-1" %}
|
||||
{% blocktrans count total=remaining %}
|
||||
And {{ total }} other recipient
|
||||
{% plural %}
|
||||
And {{ total }} other recipients
|
||||
{% endblocktrans %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if form.to.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{% for error in form.to.errors %}
|
||||
<span>{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Subject Field -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.subject.id_for_label }}" class="form-label fw-bold">
|
||||
{% trans "Subject" %}
|
||||
</label>
|
||||
{{ form.subject }}
|
||||
{% if form.subject.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{% for error in form.subject.errors %}
|
||||
<span>{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Message Field -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.message.id_for_label }}" class="form-label fw-bold">
|
||||
{% trans "Message" %}
|
||||
</label>
|
||||
{{ form.message }}
|
||||
{% if form.message.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{% for error in form.message.errors %}
|
||||
<span>{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div class="text-muted small">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{% trans "Email will be sent to all selected recipients" %}
|
||||
</div>
|
||||
<div>
|
||||
<button type="button"
|
||||
class="btn btn-secondary me-2"
|
||||
data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-1"></i>
|
||||
{% trans "Cancel" %}
|
||||
</button>
|
||||
<button type="submit"
|
||||
class="btn btn-primary"
|
||||
id="send-email-btn">
|
||||
<i class="fas fa-paper-plane me-1"></i>
|
||||
{% trans "Send Email" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="w-full max-w-2xl mx-auto">
|
||||
<!-- Messages -->
|
||||
{% if messages %}
|
||||
<div class="space-y-2 mb-4" role="alert" aria-live="polite">
|
||||
{% for message in messages %}
|
||||
<div class="alert {% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-800{% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-800{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-800{% else %}bg-blue-50 border-blue-200 text-blue-800{% endif %} border rounded-lg px-4 py-3 flex items-start justify-between gap-2">
|
||||
<span class="flex-1 text-sm">{{ message }}</span>
|
||||
<button type="button"
|
||||
class="text-gray-400 hover:text-gray-600 p-1 shrink-0 touch-target"
|
||||
onclick="this.parentElement.remove()"
|
||||
aria-label="{% trans 'Dismiss' %}">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- 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 %}"
|
||||
hx-include="#application-form"
|
||||
hx-push-url="false"
|
||||
hx-swap="outerHTML"
|
||||
hx-on::after-request="closeEmailModal()"
|
||||
class="space-y-6">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Recipients Field -->
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
{% trans "To" %}
|
||||
</label>
|
||||
<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 -->
|
||||
{% for choice in form.to %}
|
||||
<input type="hidden" name="{{ form.to.name }}" value="{{ choice.data.value }}">
|
||||
{% endfor %}
|
||||
|
||||
<!-- Show first recipient only -->
|
||||
{% for choice in form.to|slice:":1" %}
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<div class="w-4 h-4 rounded border-2 border-temple-red bg-temple-red flex items-center justify-center">
|
||||
<i data-lucide="check" class="w-3 h-3 text-white"></i>
|
||||
</div>
|
||||
<span class="text-sm text-gray-700">{{ choice.choice_label }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Summary of remaining recipients -->
|
||||
{% if form.to|length > 1 %}
|
||||
<div class="flex items-center gap-2 text-gray-600 text-sm mt-2 pt-2 border-t border-gray-200">
|
||||
<i data-lucide="info" class="w-4 h-4 text-temple-red"></i>
|
||||
{% with remaining=form.to|length|add:"-1" %}
|
||||
{% blocktrans count total=remaining %}
|
||||
And {{ total }} other recipient
|
||||
{% plural %}
|
||||
And {{ total }} other recipients
|
||||
{% endblocktrans %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if form.to.errors %}
|
||||
<div class="text-red-600 text-sm mt-2">
|
||||
{% for error in form.to.errors %}
|
||||
<span class="block">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Subject Field -->
|
||||
<div>
|
||||
<label for="{{ form.subject.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
{% trans "Subject" %}
|
||||
</label>
|
||||
<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 %}
|
||||
<div class="text-red-600 text-sm mt-2">
|
||||
{% for error in form.subject.errors %}
|
||||
<span class="block">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message Field -->
|
||||
<div>
|
||||
<label for="{{ form.message.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
{% trans "Message" %}
|
||||
</label>
|
||||
<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 %}
|
||||
<div class="text-red-600 text-sm mt-2">
|
||||
{% for error in form.message.errors %}
|
||||
<span class="block">{{ error }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Actions -->
|
||||
<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="flex items-center gap-2 text-gray-600 text-sm">
|
||||
<i data-lucide="info" class="w-4 h-4 text-temple-red"></i>
|
||||
{% trans "Email will be sent to all selected recipients" %}
|
||||
</div>
|
||||
<div class="flex items-center gap-3 w-full sm:w-auto">
|
||||
<button type="button"
|
||||
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"
|
||||
onclick="closeEmailModal()">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
{% trans "Cancel" %}
|
||||
</button>
|
||||
<button type="submit"
|
||||
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">
|
||||
<i data-lucide="send" class="w-4 h-4"></i>
|
||||
{% trans "Send Email" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading Overlay -->
|
||||
<div id="email-loading-overlay" class="d-none">
|
||||
<div class="d-flex justify-content-center align-items-center" style="min-height: 200px;">
|
||||
<div class="text-center">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">{% trans "Loading..." %}</span>
|
||||
<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="bg-white rounded-xl shadow-lg p-8 max-w-sm w-full mx-4">
|
||||
<div class="flex flex-col items-center gap-4">
|
||||
<div class="w-12 h-12 rounded-full border-4 border-temple-red border-t-transparent animate-spin" role="status">
|
||||
<span class="sr-only">{% trans "Loading..." %}</span>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
{% trans "Sending email..." %}
|
||||
<div class="text-center">
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
|
||||
<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>
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('email-compose-form1');
|
||||
const sendBtn = document.getElementById('send-email-btn1');
|
||||
const form = document.getElementById('email-compose-form');
|
||||
const sendBtn = document.getElementById('send-email-btn');
|
||||
const loadingOverlay = document.getElementById('email-loading-overlay');
|
||||
const messagesContainer = document.getElementById('email-messages-container');
|
||||
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
@ -219,11 +183,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Show loading state
|
||||
if (sendBtn) {
|
||||
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) {
|
||||
loadingOverlay.classList.remove('d-none');
|
||||
loadingOverlay.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Clear previous messages
|
||||
@ -247,29 +212,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Hide loading state
|
||||
if (sendBtn) {
|
||||
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) {
|
||||
loadingOverlay.classList.add('d-none');
|
||||
loadingOverlay.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Show result message
|
||||
if (data.success) {
|
||||
showMessage(data.message || 'Email sent successfully!', 'success');
|
||||
if (data.success || data.status === 'queued') {
|
||||
showMessage(data.message || 'Email enqueued successfully!', 'success');
|
||||
|
||||
// Close modal after a short delay
|
||||
setTimeout(() => {
|
||||
const modal = form.closest('.modal');
|
||||
if (modal) {
|
||||
const bootstrapModal = bootstrap.Modal.getInstance(modal);
|
||||
if (bootstrapModal) {
|
||||
bootstrapModal.hide();
|
||||
}
|
||||
}
|
||||
closeEmailModal();
|
||||
}, 1500);
|
||||
} 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 => {
|
||||
@ -278,11 +238,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Hide loading state
|
||||
if (sendBtn) {
|
||||
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) {
|
||||
loadingOverlay.classList.add('d-none');
|
||||
loadingOverlay.classList.add('hidden');
|
||||
}
|
||||
|
||||
showMessage('An error occurred while sending the email. Please try again.', 'danger');
|
||||
@ -293,116 +254,62 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function showMessage(message, type) {
|
||||
if (!messagesContainer) return;
|
||||
|
||||
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger';
|
||||
const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-triangle';
|
||||
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' ? 'check-circle' : 'alert-triangle';
|
||||
|
||||
const messageHtml = `
|
||||
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
|
||||
<i class="fas ${icon} me-2"></i>
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
const messageDiv = document.createElement('div');
|
||||
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`;
|
||||
messageDiv.innerHTML = `
|
||||
<i data-lucide="${icon}" class="w-5 h-5 shrink-0 mt-0.5"></i>
|
||||
<span class="flex-1 text-sm">${message}</span>
|
||||
<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
|
||||
if (type === 'success') {
|
||||
setTimeout(() => {
|
||||
const alert = messagesContainer.querySelector('.alert');
|
||||
if (alert) {
|
||||
const bsAlert = new bootstrap.Alert(alert);
|
||||
bsAlert.close();
|
||||
}
|
||||
messageDiv.style.opacity = '0';
|
||||
messageDiv.style.transform = 'translateX(100%)';
|
||||
setTimeout(() => messageDiv.remove(), 300);
|
||||
}, 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
|
||||
function setupCharacterCounter() {
|
||||
const messageField = form.querySelector('#{{ form.message.id_for_label }}');
|
||||
if (!messageField) return;
|
||||
|
||||
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';
|
||||
|
||||
messageField.parentNode.appendChild(counter);
|
||||
|
||||
function updateCounter() {
|
||||
const length = messageField.value.length;
|
||||
const maxLength = 5000; // Adjust as needed
|
||||
const maxLength = 5000;
|
||||
counter.textContent = `${length} / ${maxLength} characters`;
|
||||
|
||||
if (length > maxLength * 0.9) {
|
||||
counter.classList.add('text-warning');
|
||||
counter.classList.remove('text-muted');
|
||||
counter.className = 'text-yellow-600 text-sm mt-2 text-right';
|
||||
} else {
|
||||
counter.classList.remove('text-warning');
|
||||
counter.classList.add('text-muted');
|
||||
counter.className = 'text-gray-500 text-sm mt-2 text-right';
|
||||
}
|
||||
}
|
||||
|
||||
messageField.addEventListener('input', updateCounter);
|
||||
updateCounter(); // Initial count
|
||||
updateCounter();
|
||||
}
|
||||
|
||||
// Auto-save functionality
|
||||
@ -417,17 +324,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const draftData = {
|
||||
subject: subject.value,
|
||||
message: message.value,
|
||||
recipients: Array.from(form.querySelectorAll('input[name="{{ form.recipients.name }}"]:checked')).map(cb => cb.value),
|
||||
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
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
localStorage.setItem('email_draft_' + window.location.pathname, JSON.stringify(draftData));
|
||||
}
|
||||
|
||||
function autoSave() {
|
||||
clearTimeout(autoSaveTimer);
|
||||
autoSaveTimer = setTimeout(saveDraft, 2000); // Save after 2 seconds of inactivity
|
||||
autoSaveTimer = setTimeout(saveDraft, 2000);
|
||||
}
|
||||
|
||||
[subject, message].forEach(field => {
|
||||
@ -450,21 +354,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (subject && draft.subject) subject.value = draft.subject;
|
||||
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
|
||||
showMessage('Draft restored from local storage', 'success');
|
||||
} catch (e) {
|
||||
@ -484,16 +373,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
setTimeout(loadDraft, 100);
|
||||
|
||||
// Clear draft on successful submission
|
||||
const originalSubmitHandler = form.onsubmit;
|
||||
form.addEventListener('submit', function(e) {
|
||||
const isValid = validateForm();
|
||||
if (!isValid) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Clear draft on successful submission
|
||||
form.addEventListener('submit', function() {
|
||||
setTimeout(clearDraft, 2000);
|
||||
});
|
||||
|
||||
@ -509,16 +389,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Escape to cancel/close modal
|
||||
if (e.key === 'Escape') {
|
||||
const modal = form.closest('.modal');
|
||||
if (modal) {
|
||||
const bootstrapModal = bootstrap.Modal.getInstance(modal);
|
||||
if (bootstrapModal) {
|
||||
bootstrapModal.hide();
|
||||
}
|
||||
}
|
||||
closeEmailModal();
|
||||
}
|
||||
});
|
||||
|
||||
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>
|
||||
@ -1,18 +1,18 @@
|
||||
{% load i18n %}
|
||||
|
||||
<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 }}">
|
||||
<button name="language" value="en" class="dropdown-item {% if LANGUAGE_CODE == 'en' %}active bg-light-subtle{% endif %}" type="submit">
|
||||
<span class="me-2">🇺🇸</span> English
|
||||
<button 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="mr-2">🇺🇸</span> English
|
||||
</button>
|
||||
</form>
|
||||
</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 }}">
|
||||
<button name="language" value="ar" class="dropdown-item {% if LANGUAGE_CODE == 'ar' %}active bg-light-subtle{% endif %}" type="submit">
|
||||
<span class="me-2">🇸🇦</span> العربية
|
||||
<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="mr-2">🇸🇦</span> العربية
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
@ -1,44 +1,45 @@
|
||||
{% load i18n %}
|
||||
<!-- 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 %}
|
||||
<input type="hidden" name="candidate_pk" value="{{ candidate.pk }}">
|
||||
{% if scheduled_interview %}
|
||||
<input type="hidden" name="interview_pk" value="{{ scheduled_interview.pk }}">
|
||||
{% endif %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_topic" class="form-label">{% trans "Topic" %}</label>
|
||||
<input type="text" class="form-control" id="id_topic" name="topic" value="{{ initial_data.topic|default:'' }}" required>
|
||||
<div class="mb-4">
|
||||
<label for="id_topic" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Topic" %}</label>
|
||||
<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 class="mb-3">
|
||||
<label for="id_start_time" class="form-label">{% 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>
|
||||
<div class="mb-4">
|
||||
<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="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 class="mb-3">
|
||||
<label for="id_duration" class="form-label">{% 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>
|
||||
<div class="mb-4">
|
||||
<label for="id_duration" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Duration (minutes)" %}</label>
|
||||
<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 id="meetingDetails" class="alert alert-info" style="display: none;">
|
||||
<strong>{% trans "Meeting Details (will appear after scheduling):" %}</strong>
|
||||
<p><strong>{% trans "Join URL:" %}</strong> <a id="joinUrlDisplay" href="#" target="_blank"></a></p>
|
||||
<p><strong>{% trans "Meeting ID:" %}</strong> <span id="meetingIdDisplay"></span></p>
|
||||
<div id="meetingDetails" class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4" style="display: none;">
|
||||
<strong class="block text-blue-900 mb-2">{% trans "Meeting Details (will appear after scheduling):" %}</strong>
|
||||
<p class="mb-1"><strong>{% trans "Join URL:" %}</strong> <a id="joinUrlDisplay" href="#" target="_blank" class="text-temple-red hover:underline"></a></p>
|
||||
<p class="mb-0"><strong>{% trans "Meeting ID:" %}</strong> <span id="meetingIdDisplay"></span></p>
|
||||
</div>
|
||||
|
||||
<div id="successMessage" class="alert alert-success" style="display: none;">
|
||||
<span id="successText"></span>
|
||||
<small><a id="joinLinkSuccess" href="#" target="_blank" style="color: inherit; text-decoration: underline;">{% trans "Click here to join meeting" %}</a></small>
|
||||
<div id="successMessage" class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4" style="display: none;">
|
||||
<span id="successText" class="block text-green-900"></span>
|
||||
<small><a id="joinLinkSuccess" href="#" target="_blank" class="text-green-700 hover:underline">{% trans "Click here to join meeting" %}</a></small>
|
||||
</div>
|
||||
|
||||
<div id="errorMessage" class="alert alert-danger" style="display: none;">
|
||||
<span id="errorText"></span>
|
||||
<div id="errorMessage" class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4" style="display: none;">
|
||||
<span id="errorText" class="block text-red-900"></span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-dismiss="modal">{% trans "Cancel" %}</button>
|
||||
<button type="submit" class="btn btn-primary" id="scheduleBtn">
|
||||
<div class="flex items-center justify-end gap-3 mt-4">
|
||||
<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="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 %}
|
||||
</button>
|
||||
</div>
|
||||
@ -57,20 +58,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const joinLinkSuccess = document.getElementById('joinLinkSuccess');
|
||||
const errorMessageDiv = document.getElementById('errorMessage');
|
||||
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) {
|
||||
event.preventDefault();
|
||||
@ -80,12 +69,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
errorMessageDiv.style.display = 'none';
|
||||
|
||||
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);
|
||||
// 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 url = form.action.replace(/\?.*$/, ""); // Remove any existing query params like _target
|
||||
const url = form.action.replace(/\?.*$/, "");
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
@ -95,10 +83,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
})
|
||||
.then(response => response.json()) // Always expect JSON for HTMX success/error
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
scheduleBtn.disabled = false;
|
||||
scheduleBtn.innerHTML = submitBtnText; // Reset to original text
|
||||
scheduleBtn.textContent = submitBtnText;
|
||||
|
||||
if (data.success) {
|
||||
successText.textContent = data.message;
|
||||
@ -107,24 +95,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
joinUrlDisplay.textContent = data.join_url;
|
||||
joinUrlDisplay.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) {
|
||||
meetingIdDisplay.textContent = data.meeting_id;
|
||||
}
|
||||
|
||||
if (isModalTarget && modalElement) {
|
||||
const bsModal = bootstrap.Modal.getInstance(modalElement);
|
||||
if (bsModal) bsModal.hide();
|
||||
if (isModalTarget) {
|
||||
const modalElement = document.getElementById('meetingModal');
|
||||
if (modalElement) {
|
||||
modalElement.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
// Optionally, trigger an event on the parent page to update its list
|
||||
if (window.parent && window.parent.dispatchEvent) {
|
||||
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 {
|
||||
errorText.textContent = data.error || '{% trans "An unknown error occurred." %}';
|
||||
errorMessageDiv.style.display = 'block';
|
||||
@ -133,13 +118,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
scheduleBtn.disabled = false;
|
||||
scheduleBtn.innerHTML = submitBtnText;
|
||||
scheduleBtn.textContent = submitBtnText;
|
||||
errorText.textContent = '{% trans "An error occurred while processing your request." %}';
|
||||
errorMessageDiv.style.display = 'block';
|
||||
});
|
||||
});
|
||||
|
||||
// Repopulate if initial_data was passed (for rescheduling)
|
||||
{% if initial_data %}
|
||||
document.getElementById('id_topic').value = '{{ initial_data.topic }}';
|
||||
document.getElementById('id_start_time').value = '{{ initial_data.start_time }}';
|
||||
|
||||
@ -1,63 +1,60 @@
|
||||
<div class="container mt-4">
|
||||
<h1>Schedule Interviews for {{ job.title }}</h1>
|
||||
{% load i18n %}
|
||||
<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="card-body">
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<form method="post" id="schedule-form">
|
||||
{% csrf_token %}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>{% trans "Select Candidates" %}</h5>
|
||||
<div class="form-group">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<!-- Left Column - Candidates -->
|
||||
<div>
|
||||
<h5 class="text-lg font-semibold text-gray-900 mb-4">{% trans "Select Candidates" %}</h5>
|
||||
<div class="space-y-2">
|
||||
{{ form.candidates }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h5>{% trans "Schedule Details" %}</h5>
|
||||
<!-- Right Column - Schedule Details -->
|
||||
<div>
|
||||
<h5 class="text-lg font-semibold text-gray-900 mb-4">{% trans "Schedule Details" %}</h5>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="{{ form.start_date.id_for_label }}">{% trans "Start Date" %}</label>
|
||||
{{ form.start_date }}
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="{{ form.end_date.id_for_label }}">{% trans "End Date" %}</label>
|
||||
{{ form.end_date }}
|
||||
</div>
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label>{% trans "Working Days" %}</label>
|
||||
{{ form.working_days }}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group mb-3">
|
||||
<label for="{{ form.start_time.id_for_label }}">{% trans "Start Time" %}</label>
|
||||
{{ form.start_time }}
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<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 }}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group mb-3">
|
||||
<label for="{{ form.end_time.id_for_label }}">{% trans "End Time" %}</label>
|
||||
<div>
|
||||
<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 }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "Working Days" %}</label>
|
||||
{{ form.working_days }}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="{{ form.start_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time" %}</label>
|
||||
{{ form.start_time }}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="{{ form.end_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
|
||||
{{ form.end_time }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group mb-3">
|
||||
<label for="{{ form.interview_duration.id_for_label }}">{% trans "Interview Duration (minutes)" %}</label>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="{{ form.interview_duration.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Interview Duration (minutes)" %}</label>
|
||||
{{ form.interview_duration }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="form-group mb-3">
|
||||
<label for="{{ form.buffer_time.id_for_label }}">{% trans "Buffer Time (minutes)" %}</label>
|
||||
<div>
|
||||
<label for="{{ form.buffer_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Buffer Time (minutes)" %}</label>
|
||||
{{ form.buffer_time }}
|
||||
</div>
|
||||
</div>
|
||||
@ -65,36 +62,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h5>Break Times</h5>
|
||||
<div id="break-times-container">
|
||||
{{ break_formset.management_form }}
|
||||
{% for form in break_formset %}
|
||||
<div class="break-time-form row mb-2">
|
||||
<div class="col-md-5">
|
||||
<label>{% trans "Start Time" %}</label>
|
||||
{{ form.start_time }}
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label>{% trans "End Time" %}</label>
|
||||
{{ form.end_time }}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label> </label><br>
|
||||
{{ form.DELETE }}
|
||||
<button type="button" class="btn btn-danger btn-sm remove-break">{% trans "Remove" %}</button>
|
||||
</div>
|
||||
<!-- Break Times -->
|
||||
<div class="mt-8">
|
||||
<h5 class="text-lg font-semibold text-gray-900 mb-4">{% trans "Break Times" %}</h5>
|
||||
<div id="break-times-container" class="space-y-4">
|
||||
{{ break_formset.management_form }}
|
||||
{% for form in break_formset %}
|
||||
<div class="break-time-form grid grid-cols-12 gap-4 items-start">
|
||||
<div class="col-span-5">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time" %}</label>
|
||||
{{ form.start_time }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<button type="button" id="add-break" class="btn btn-secondary btn-sm mt-2">{% trans "Add Break" %}</button>
|
||||
<div class="col-span-5">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
|
||||
{{ form.end_time }}
|
||||
</div>
|
||||
<div class="col-span-2 flex items-end">
|
||||
{{ form.DELETE }}
|
||||
<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>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<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">
|
||||
<i data-lucide="plus" class="w-4 h-4 inline mr-1"></i>{% trans "Add Break" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">{% trans "Preview Schedule" %}</button>
|
||||
<a href="{% url 'job_detail' slug=job.slug %}" class="btn btn-secondary">{% trans "Cancel" %}</a>
|
||||
<!-- Action Buttons -->
|
||||
<div class="mt-8 flex gap-3">
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
@ -110,19 +114,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
addBreakBtn.addEventListener('click', function() {
|
||||
const formCount = parseInt(totalFormsInput.value);
|
||||
const newFormHtml = `
|
||||
<div class="break-time-form row mb-2">
|
||||
<div class="col-md-5">
|
||||
<label>Start Time</label>
|
||||
<input type="time" name="breaks-${formCount}-start_time" class="form-control" id="id_breaks-${formCount}-start_time">
|
||||
<div class="break-time-form grid grid-cols-12 gap-4 items-start">
|
||||
<div class="col-span-5">
|
||||
<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="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 class="col-md-5">
|
||||
<label>End Time</label>
|
||||
<input type="time" name="breaks-${formCount}-end_time" class="form-control" id="id_breaks-${formCount}-end_time">
|
||||
<div class="col-span-5">
|
||||
<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="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 class="col-md-2">
|
||||
<label> </label><br>
|
||||
<div class="col-span-2 flex items-end">
|
||||
<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>
|
||||
`;
|
||||
@ -133,16 +138,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
breakTimesContainer.appendChild(newForm);
|
||||
totalFormsInput.value = formCount + 1;
|
||||
lucide.createIcons();
|
||||
});
|
||||
|
||||
// Handle remove button clicks
|
||||
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 deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
|
||||
deleteCheckbox.checked = true;
|
||||
form.style.display = 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize icons
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
@ -1,5 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% 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 }}">
|
||||
{% trans "Subject" %}
|
||||
</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>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_email_form.message.id_for_label }}">
|
||||
{% trans "Message" %}
|
||||
</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 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 }}">
|
||||
{% trans "New Date" %}
|
||||
</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>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.new_time.id_for_label }}">
|
||||
{% trans "New Time" %}
|
||||
</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>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.reason.id_for_label }}">
|
||||
{% trans "Reason for Rescheduling" %}
|
||||
</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 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 }}">
|
||||
{% trans "Reason for Cancellation" %}
|
||||
</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 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 }}">
|
||||
{% trans "Interview Result" %}
|
||||
</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>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.result_comments.id_for_label }}">
|
||||
{% trans "Comments" %}
|
||||
</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 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">
|
||||
{% 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>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status.id_for_label }}">
|
||||
{% trans "Interview Status" %}
|
||||
</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>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status_notes.id_for_label }}">
|
||||
{% trans "Notes" %}
|
||||
</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 class="flex gap-3 pt-2">
|
||||
@ -617,24 +726,39 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Style form fields function
|
||||
function styleFormFields() {
|
||||
// Style text inputs
|
||||
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 => {
|
||||
// Style text inputs (including those wrapped by crispy forms)
|
||||
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')) {
|
||||
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
|
||||
document.querySelectorAll('select').forEach(select => {
|
||||
const selects = document.querySelectorAll('select');
|
||||
selects.forEach(select => {
|
||||
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
|
||||
document.querySelectorAll('textarea').forEach(textarea => {
|
||||
const textareas = document.querySelectorAll('textarea');
|
||||
textareas.forEach(textarea => {
|
||||
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');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Style form fields after modal opens
|
||||
setTimeout(() => {
|
||||
// Style form fields after modal opens with multiple retries
|
||||
setTimeout(function() {
|
||||
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.remove('text-red-600');
|
||||
|
||||
setTimeout(() => {
|
||||
setTimeout(function() {
|
||||
messageElement.textContent = '';
|
||||
}, 3000);
|
||||
} catch (e) {
|
||||
|
||||
@ -11,25 +11,25 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-6">
|
||||
<div class="container mx-auto px-3 sm:px-4 lg:px-8 py-6">
|
||||
|
||||
<!-- 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-kaauh-blue transition flex items-center gap-1">
|
||||
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Home" %}
|
||||
<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 "Dashboard" %}
|
||||
</a></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><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-temple-red font-semibold">{% trans "Applicants" %}</li>
|
||||
<li class="text-temple-red font-semibold">{% trans "Applications" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- 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-1">
|
||||
<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 items-center gap-2">
|
||||
<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>
|
||||
{% if job.max_applications %}
|
||||
<div class="flex items-center gap-2">
|
||||
@ -62,8 +62,8 @@
|
||||
|
||||
<!-- Stats Section -->
|
||||
<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="text-3xl font-bold text-kaauh-blue mb-1">{{ total_applications }}</div>
|
||||
<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-temple-red mb-1">{{ total_applications }}</div>
|
||||
<div class="text-xs text-gray-600">{% trans "Total Applications" %}</div>
|
||||
</div>
|
||||
{% 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>
|
||||
<input type="text" id="search" name="q" value="{{ search_query }}"
|
||||
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>
|
||||
<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>
|
||||
{% for key, value in stage_choices %}
|
||||
<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>
|
||||
<input type="number" id="min_ai_score" name="min_ai_score" value="{{ min_ai_score }}"
|
||||
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>
|
||||
<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 }}"
|
||||
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>
|
||||
<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 }}"
|
||||
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>
|
||||
<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 }}"
|
||||
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>
|
||||
<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 "Oldest First" %}</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 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>
|
||||
{% trans "Apply Filters" %}
|
||||
</button>
|
||||
@ -201,7 +201,7 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
{% trans "Application" %}
|
||||
</a>
|
||||
@ -236,7 +236,7 @@
|
||||
{% trans "There are currently no applicants for this job." %}
|
||||
{% endif %}
|
||||
</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>
|
||||
{% trans "Clear Filters" %}
|
||||
</a>
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-6">
|
||||
<div class="container mx-auto px-3 sm:px-4 lg:px-8 py-6">
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<nav class="mb-6" aria-label="breadcrumb">
|
||||
@ -24,18 +24,18 @@
|
||||
<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 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>
|
||||
</nav>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 mb-1 flex items-center gap-3">
|
||||
<div class="text-gray-900">
|
||||
<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">
|
||||
<i data-lucide="arrow-left" class="w-6 h-6"></i>
|
||||
</a>
|
||||
{% trans "Applicants for" %} "{{ job.title }}"
|
||||
<span>{% trans "Applicants for" %} "{{ job.title }}"</span>
|
||||
</h1>
|
||||
</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">
|
||||
|
||||
60
templates/logo/ats_logo.html
Normal file
60
templates/logo/ats_logo.html
Normal 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 |
@ -1,75 +1,81 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{{ title }} - ATS{% endblock %}
|
||||
{% block title %}{{ title }} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header bg-warning text-white">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex justify-center">
|
||||
<div class="w-full max-w-2xl">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="bg-amber-500 text-white p-5">
|
||||
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||
<i data-lucide="alert-triangle" class="w-6 h-6"></i>
|
||||
{{ title }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
{{ message }}
|
||||
<div class="p-6">
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-6 flex items-start gap-3">
|
||||
<i data-lucide="info" class="w-5 h-5 text-amber-600 shrink-0 mt-0.5"></i>
|
||||
<p class="text-amber-800">{{ message }}</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="me-3">
|
||||
<strong>{% trans "Agency:" %}</strong> {{ access_link.assignment.agency.name }}
|
||||
<div class="bg-gray-50 rounded-xl p-5 mb-6">
|
||||
<div class="flex flex-wrap items-center gap-4 mb-4">
|
||||
<div>
|
||||
<strong class="text-gray-700">{% trans "Agency:" %}</strong> {{ access_link.assignment.agency.name }}
|
||||
</div>
|
||||
<div>
|
||||
<strong class="text-gray-700">{% trans "Job:" %}</strong> {{ access_link.assignment.job.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="me-3">
|
||||
<strong>{% trans "Job:" %}</strong> {{ access_link.assignment.job.title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="me-3">
|
||||
<strong>{% trans "Current Status:" %}</strong>
|
||||
<span class="badge bg-{{ 'success' if access_link.is_active else 'danger' }}">
|
||||
{{ 'Active' if access_link.is_active else 'Inactive' }}
|
||||
</span>
|
||||
<div class="flex flex-wrap items-center gap-4 mb-4">
|
||||
<div>
|
||||
<strong class="text-gray-700">{% trans "Current Status:" %}</strong>
|
||||
<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' }}">
|
||||
{% if access_link.is_active %}
|
||||
{% trans "Active" %}
|
||||
{% else %}
|
||||
{% trans "Inactive" %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="me-3">
|
||||
<strong>{% trans "Expires:" %}</strong> {{ access_link.expires_at|date:"Y-m-d H:i" }}
|
||||
<div class="flex flex-wrap items-center gap-4 mb-4">
|
||||
<div>
|
||||
<strong class="text-gray-700">{% trans "Expires:" %}</strong> {{ access_link.expires_at|date:"Y-m-d H:i" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="me-3">
|
||||
<strong>{% trans "Access Count:" %}</strong> {{ access_link.access_count }}
|
||||
<div class="flex flex-wrap items-center gap-4 mb-4">
|
||||
<div>
|
||||
<strong class="text-gray-700">{% trans "Access Count:" %}</strong> {{ access_link.access_count }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="me-3">
|
||||
<strong>{% trans "Last Accessed:" %}</strong>
|
||||
{% if access_link.last_accessed %}
|
||||
{{ access_link.last_accessed|date:"Y-m-d H:i" }}
|
||||
{% else %}
|
||||
{% trans "Never" %}
|
||||
{% endif %}
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<div>
|
||||
<strong class="text-gray-700">{% trans "Last Accessed:" %}</strong>
|
||||
{% if access_link.last_accessed %}
|
||||
{{ access_link.last_accessed|date:"Y-m-d H:i" }}
|
||||
{% else %}
|
||||
{% trans "Never" %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url request.resolver_match.url_name %}">
|
||||
{% csrf_token %}
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<a href="{{ cancel_url }}" class="btn btn-secondary">
|
||||
<i class="fas fa-times me-2"></i>
|
||||
<div class="flex flex-col sm:flex-row gap-3 justify-end">
|
||||
<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 data-lucide="x" class="w-4 h-4"></i>
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-warning">
|
||||
<i class="fas fa-{{ 'toggle-on' if title == 'Reactivate Access Link' else 'toggle-off' }} me-2"></i>
|
||||
<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 data-lucide="{% if title == 'Reactivate Access Link' %}toggle-right{% else %}toggle-left{% endif %}" class="w-4 h-4"></i>
|
||||
{{ title }}
|
||||
</button>
|
||||
</div>
|
||||
@ -79,4 +85,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -4,184 +4,151 @@
|
||||
{% block title %}{% trans "Access Link Details" %} - ATS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 px-3 py-3">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6 px-3 py-3">
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-link 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="link" class="w-8 h-8 text-temple-red"></i>
|
||||
</div>
|
||||
{% trans "Access Link Details" %}
|
||||
</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>
|
||||
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Assignment" %}
|
||||
<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 data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Assignment" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="kaauh-card shadow-sm mb-4 px-3 py-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-shield-alt me-2 text-primary"></i>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Main Content -->
|
||||
<div class="lg:col-span-2">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-start mb-6">
|
||||
<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" %}
|
||||
</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 %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-muted small">{% trans "Assignment" %}</label>
|
||||
<div class="fw-semibold">
|
||||
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="text-decoration-none">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Assignment" %}</label>
|
||||
<div class="font-semibold text-gray-900">
|
||||
<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 }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-muted small">{% trans "Agency" %}</label>
|
||||
<div class="fw-semibold">{{ access_link.assignment.agency.name }}</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Agency" %}</label>
|
||||
<div class="font-semibold text-gray-900">{{ access_link.assignment.agency.name }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-muted small">{% trans "Created At" %}</label>
|
||||
<div class="fw-semibold">{{ access_link.created_at|date:"Y-m-d H:i" }}</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Created At" %}</label>
|
||||
<div class="font-semibold text-gray-900">{{ access_link.created_at|date:"Y-m-d H:i" }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-muted small">{% trans "Expires At" %}</label>
|
||||
<div class="fw-semibold {% if access_link.is_expired %}text-danger{% endif %}">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Expires At" %}</label>
|
||||
<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" }}
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-muted small">{% trans "Max Candidates" %}</label>
|
||||
<div class="fw-semibold">{{ access_link.assignment.max_candidates }}</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Max Candidates" %}</label>
|
||||
<div class="font-semibold text-gray-900">{{ access_link.assignment.max_candidates }}</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label text-muted small">{% trans "Candidates Submitted" %}</label>
|
||||
<div class="fw-semibold">{{ access_link.assignment.candidates_submitted }}</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Candidates Submitted" %}</label>
|
||||
<div class="font-semibold text-gray-900">{{ access_link.assignment.candidates_submitted }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% comment %} <div class="kaauh-card shadow-sm">
|
||||
<div class="card-body px-3 py-3">
|
||||
<h5 class="card-title mb-3">
|
||||
<i class="fas fa-key me-2 text-warning"></i>
|
||||
{% trans "Access Credentials" %}
|
||||
</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<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 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>
|
||||
<!-- Sidebar -->
|
||||
<div class="lg:col-span-1 space-y-6">
|
||||
<!-- Usage Statistics -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<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">
|
||||
<i data-lucide="trending-up" class="w-5 h-5 text-blue-600"></i>
|
||||
</div>
|
||||
{% trans "Usage Statistics" %}
|
||||
</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="text-muted">{% trans "Total Accesses" %}</span>
|
||||
<span class="fw-semibold">{{ access_link.access_count }}</span>
|
||||
<div class="space-y-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">{% trans "Total Accesses" %}</span>
|
||||
<span class="font-bold text-gray-900">{{ access_link.access_count }}</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="text-muted">{% trans "Last Accessed" %}</span>
|
||||
<span class="fw-semibold">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">{% trans "Last Accessed" %}</span>
|
||||
<span class="font-semibold text-gray-900">
|
||||
{% if access_link.last_accessed %}
|
||||
{{ access_link.last_accessed|date:"Y-m-d H:i" }}
|
||||
{% else %}
|
||||
<span class="text-muted">{% trans "Never" %}</span>
|
||||
<span class="text-gray-500">{% trans "Never" %}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<span class="text-muted">{% trans "Submissions" %}</span>
|
||||
<span class="fw-semibold">{{ access_link.assignment.candidates_submitted }}/{{ access_link.assignment.max_candidates }}</span>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-gray-600">{% trans "Submissions" %}</span>
|
||||
<span class="font-bold text-gray-900">{{ access_link.assignment.candidates_submitted }}/{{ access_link.assignment.max_candidates }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress" style="height: 8px;">
|
||||
{% 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 %}"
|
||||
style="width: {{ progress_percent }}%"></div>
|
||||
{% widthratio access_link.assignment.candidates_submitted access_link.assignment.max_candidates 100 as progress_percent %}
|
||||
<div class="mt-4">
|
||||
<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 class="kaauh-card shadow-sm px-3 py-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-3">
|
||||
<i class="fas fa-cog me-2 text-secondary"></i>
|
||||
<!-- Actions -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<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" %}
|
||||
</h5>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Assignment" %}
|
||||
<div class="space-y-3">
|
||||
<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 data-lucide="eye" class="w-4 h-4"></i> {% trans "View Assignment" %}
|
||||
</a>
|
||||
|
||||
{% if access_link.is_active and not access_link.is_expired %}
|
||||
<button class="btn btn-warning btn-sm" onclick="confirmDeactivate()">
|
||||
<i class="fas fa-pause me-1"></i> {% trans "Deactivate" %}
|
||||
<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 data-lucide="pause" class="w-4 h-4"></i> {% trans "Deactivate" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if access_link.is_expired or not access_link.is_active %}
|
||||
<button class="btn btn-success btn-sm" onclick="confirmReactivate()">
|
||||
<i class="fas fa-play me-1"></i> {% trans "Reactivate" %}
|
||||
<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 data-lucide="play" class="w-4 h-4"></i> {% trans "Reactivate" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -190,41 +157,22 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<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() {
|
||||
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 %}';
|
||||
}
|
||||
}
|
||||
|
||||
function confirmReactivate() {
|
||||
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 %}';
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -3,379 +3,255 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="px-4 py-6">
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-tasks me-2"></i>
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-6">
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl font-bold text-temple-dark mb-1 flex items-center gap-2">
|
||||
<i data-lucide="tasks" class="w-7 h-7"></i>
|
||||
{{ assignment.agency.name }} - {{ assignment.job.title }}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
<p class="text-gray-600">
|
||||
{% trans "Assignment Details and Management" %}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'agency_assignment_list' %}" class="btn btn-outline-secondary me-2">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Assignments" %}
|
||||
<div class="flex gap-2">
|
||||
<a href="{% url 'agency_assignment_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 data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||
{% trans "Back to Assignments" %}
|
||||
</a>
|
||||
<a href="{% url 'agency_assignment_update' assignment.slug %}" class="btn btn-main-action">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Assignment" %}
|
||||
<a href="{% url 'agency_assignment_update' assignment.slug %}"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<!-- Assignment Overview -->
|
||||
<div class="col-lg-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Main Content -->
|
||||
<div class="lg:col-span-2 space-y-6">
|
||||
<!-- Assignment Details Card -->
|
||||
<div class="kaauh-card p-4 mb-4">
|
||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
{% trans "Assignment Details" %}
|
||||
</h5>
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||
<div class="p-6">
|
||||
<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" %}
|
||||
</h5>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<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 class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="space-y-4">
|
||||
<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 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<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" }}
|
||||
<div>
|
||||
<label class="text-sm text-gray-500 font-medium block mb-1">{% trans "Deadline" %}</label>
|
||||
<div class="{% if assignment.is_expired %}text-red-600{% else %}text-gray-600{% endif %} flex items-center gap-1">
|
||||
<i data-lucide="calendar" class="w-4 h-4"></i>
|
||||
{{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
||||
</div>
|
||||
{% if assignment.is_expired %}
|
||||
<span class="text-sm text-red-600 flex items-center gap-1 mt-1">
|
||||
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
|
||||
{% trans "Expired" %}
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if assignment.is_expired %}
|
||||
<small class="text-danger">
|
||||
<i class="fas fa-exclamation-triangle me-1"></i>{% trans "Expired" %}
|
||||
</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if assignment.admin_notes %}
|
||||
<div class="mt-3 pt-3 border-top">
|
||||
<label class="text-muted small">{% trans "Admin Notes" %}</label>
|
||||
<div class="text-muted">{{ assignment.admin_notes }}</div>
|
||||
</div>
|
||||
{% 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 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>
|
||||
{% if assignment.admin_notes %}
|
||||
<div class="mt-6 pt-4 border-t border-gray-200">
|
||||
<label class="text-sm text-gray-500 font-medium block mb-2">{% trans "Admin Notes" %}</label>
|
||||
<div class="text-gray-700">{{ assignment.admin_notes }}</div>
|
||||
</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 -->
|
||||
<div class="kaauh-card p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-users me-2"></i>
|
||||
{% trans "Submitted Applications" %} ({{ total_applications }})
|
||||
</h5>
|
||||
{% if access_link %}
|
||||
<a href="{% url 'agency_portal_login' %}" target="_blank" class="btn btn-outline-info btn-sm">
|
||||
<i class="fas fa-external-link-alt me-1"></i> {% trans "Preview Portal" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if applications %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Application"%}</th>
|
||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Contact" %}
|
||||
</th>
|
||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Stage" %}
|
||||
</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>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for application in applications %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold">{{ application.name }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="small">
|
||||
<div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div>
|
||||
<div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="small text-muted">
|
||||
<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 class="px-4">
|
||||
<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 %}"
|
||||
class="btn btn-sm btn-outline-primary" title="{% trans 'View Details' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Applications Card -->
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<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 }})
|
||||
</h5>
|
||||
{% if access_link %}
|
||||
<a href="{% url 'agency_portal_login' %}" target="_blank"
|
||||
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>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-users fa-2x text-muted mb-3"></i>
|
||||
<h6 class="text-muted">{% trans "No applications submitted yet" %}</h6>
|
||||
<p class="text-muted small">
|
||||
{% trans "Applications will appear here once the agency submits them through their portal." %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if applications %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200">
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||
{% trans "Application" %}
|
||||
</th>
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
{% for application in applications %}
|
||||
<tr class="hover:bg-gray-50 transition">
|
||||
<td class="px-4 py-3">
|
||||
<div class="font-semibold text-gray-900">{{ application.name }}</div>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<div class="text-sm text-gray-600 space-y-1">
|
||||
<div class="flex items-center gap-1">
|
||||
<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>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<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 class="px-4 py-3">
|
||||
<span class="text-sm text-gray-600">{{ application.created_at|date:"M d, Y" }}</span>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<a href="{% url 'application_detail' application.slug %}"
|
||||
class="inline-flex items-center justify-center p-2 text-temple-red hover:bg-red-50 rounded-lg transition"
|
||||
title="{% trans 'View Details' %}">
|
||||
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-center py-12">
|
||||
<i data-lucide="users" class="w-16 h-16 text-gray-400 mx-auto mb-4"></i>
|
||||
<h6 class="text-lg font-semibold text-gray-600 mb-2">{% trans "No applications submitted yet" %}</h6>
|
||||
<p class="text-gray-500 text-sm">
|
||||
{% trans "Applications will appear here once agency submits them through their portal." %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="space-y-6">
|
||||
<!-- Progress Card -->
|
||||
<div class="kaauh-card p-4 mb-4">
|
||||
<h5 class="mb-4 text-center" style="color: var(--kaauh-teal-dark);">
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200 p-6">
|
||||
<h5 class="text-lg font-semibold text-temple-dark mb-4 text-center">
|
||||
{% trans "Submission Progress" %}
|
||||
</h5>
|
||||
|
||||
<div class="text-center mb-3">
|
||||
<div class="progress-ring">
|
||||
<svg width="120" height="120">
|
||||
<circle class="progress-ring-circle"
|
||||
stroke="#e9ecef"
|
||||
<div class="text-center mb-4">
|
||||
<div class="relative inline-block">
|
||||
<svg width="120" height="120" class="transform -rotate-90">
|
||||
<circle cx="60" cy="60" r="52"
|
||||
stroke="#e5e7eb"
|
||||
stroke-width="8"
|
||||
fill="transparent"/>
|
||||
<circle cx="60" cy="60" r="52"
|
||||
stroke="#9d2235"
|
||||
stroke-width="8"
|
||||
fill="transparent"
|
||||
r="52"
|
||||
cx="60"
|
||||
cy="60"/>
|
||||
<circle class="progress-ring-circle"
|
||||
stroke="var(--kaauh-teal)"
|
||||
stroke-width="8"
|
||||
fill="transparent"
|
||||
r="52"
|
||||
cx="60"
|
||||
cy="60"
|
||||
stroke-linecap="round"
|
||||
style="stroke-dasharray: 326.73; stroke-dashoffset: {{ stroke_dashoffset }};"/>
|
||||
</svg>
|
||||
<div class="position-absolute top-50 start-50 translate-middle text-center">
|
||||
<div class="h3 fw-bold mb-0 text-dark">{{ total_applications }}</div>
|
||||
<div class="small text-muted text-uppercase">{% trans "of" %} {{ assignment.max_candidates}}</div>
|
||||
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center">
|
||||
<div class="text-3xl font-bold text-gray-900">{{ total_applications }}</div>
|
||||
<div class="text-xs text-gray-500 uppercase">{% trans "of" %} {{ assignment.max_candidates }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="h4 mb-1">{{ total_applications }}</div>
|
||||
<div class="text-muted">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
|
||||
<div class="text-center mb-4">
|
||||
<div class="text-4xl font-bold text-temple-red mb-1">{{ total_applications }}</div>
|
||||
<div class="text-gray-600">/ {{ 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>
|
||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||
<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>
|
||||
|
||||
|
||||
<!-- Actions Card -->
|
||||
<div class="kaauh-card p-4">
|
||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-cog me-2"></i>
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200 p-6">
|
||||
<h5 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||
<i data-lucide="settings" class="w-5 h-5"></i>
|
||||
{% trans "Actions" %}
|
||||
</h5>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<div class="space-y-2">
|
||||
<a href="{% url "message_list" %}"
|
||||
class="btn btn-outline-primary">
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Send Message" %}
|
||||
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 data-lucide="mail" class="w-4 h-4"></i>
|
||||
{% trans "Send Message" %}
|
||||
</a>
|
||||
|
||||
{% if assignment.is_active and not assignment.is_expired %}
|
||||
<button type="button" class="btn btn-outline-warning"
|
||||
data-bs-toggle="modal" data-bs-target="#extendDeadlineModal">
|
||||
<i class="fas fa-clock me-1"></i> {% trans "Extend Deadline" %}
|
||||
<button type="button"
|
||||
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"
|
||||
onclick="document.getElementById('extendDeadlineModal').classList.remove('hidden')">
|
||||
<i data-lucide="clock" class="w-4 h-4"></i>
|
||||
{% trans "Extend Deadline" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
{% if assignment.is_active and assignment.status == 'ACTIVE' %}
|
||||
<button type="button" class="btn btn-danger"
|
||||
data-bs-toggle="modal" data-bs-target="#cancelAssignmentModal">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel Assignment" %}
|
||||
<button type="button"
|
||||
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"
|
||||
onclick="document.getElementById('cancelAssignmentModal').classList.remove('hidden')">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
{% trans "Cancel Assignment" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<a href="{% url 'agency_assignment_update' assignment.slug %}"
|
||||
class="btn btn-outline-secondary">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Assignment" %}
|
||||
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 data-lucide="edit-2" class="w-4 h-4"></i>
|
||||
{% trans "Edit Assignment" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@ -384,35 +260,35 @@
|
||||
|
||||
<!-- Messages Section -->
|
||||
{% if messages_ %}
|
||||
<div class="kaauh-card p-4 mt-4">
|
||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-comments me-2"></i>
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200 p-6 mt-6">
|
||||
<h5 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||
<i data-lucide="message-square" class="w-5 h-5"></i>
|
||||
{% trans "Recent Messages" %}
|
||||
</h5>
|
||||
|
||||
<div class="row">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{% for message in messages_|slice:":6" %}
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="message-item {% if not message.is_read %}unread{% endif %}">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<div class="fw-bold">{{ message.subject }}</div>
|
||||
<small class="text-muted">{{ message.created_at|date:"Y-m-d H:i" }}</small>
|
||||
</div>
|
||||
<div class="small text-muted mb-2">
|
||||
{% trans "From" %}: {{ message.sender.get_full_name }}
|
||||
</div>
|
||||
<div class="small">{{ message.message|truncatewords:30 }}</div>
|
||||
{% if not message.is_read %}
|
||||
<span class="badge bg-info mt-2">{% trans "New" %}</span>
|
||||
{% endif %}
|
||||
<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="flex justify-between items-start mb-2">
|
||||
<div class="font-semibold text-gray-900">{{ message.subject }}</div>
|
||||
<span class="text-sm text-gray-500">{{ message.created_at|date:"Y-m-d H:i" }}</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 mb-2">
|
||||
{% trans "From" %}: {{ message.sender.get_full_name }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-700">{{ message.message|truncatewords:30 }}</div>
|
||||
{% if not message.is_read %}
|
||||
<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 %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if messages_.count > 6 %}
|
||||
<div class="text-center mt-3">
|
||||
<a href="#" class="btn btn-outline-primary btn-sm">
|
||||
<div class="text-center mt-4">
|
||||
<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" %}
|
||||
</a>
|
||||
</div>
|
||||
@ -422,56 +298,108 @@
|
||||
</div>
|
||||
|
||||
<!-- Cancel Assignment Modal -->
|
||||
<div class="modal fade" id="cancelAssignmentModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
{% trans "Cancel Assignment" %}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-warning mb-3">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>{% trans "Warning:" %}</strong>
|
||||
{% trans "This action cannot be undone. The agency will no longer be able to submit applications." %}
|
||||
</div>
|
||||
<div id="cancelAssignmentModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
|
||||
<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 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('cancelAssignmentModal').classList.add('hidden')"></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 }}
|
||||
<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">
|
||||
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||
<div class="sm:flex sm:items-start">
|
||||
<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">
|
||||
<i data-lucide="alert-triangle" class="w-6 h-6 text-red-600"></i>
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||
{% 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>
|
||||
{% trans "This action cannot be undone. The agency will no longer be able to submit applications." %}
|
||||
</p>
|
||||
</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>
|
||||
|
||||
<form method="post" action="{% url 'agency_assignment_cancel' assignment.slug %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<label for="cancel_reason" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
<i data-lucide="message-square" class="w-4 h-4 inline mr-1"></i>
|
||||
{% trans "Cancellation Reason" %}
|
||||
<span class="font-normal text-gray-500">({% trans "Optional" %})</span>
|
||||
</label>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<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('cancelAssignmentModal').classList.add('hidden')">
|
||||
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||
{% trans "Cancel" %}
|
||||
</button>
|
||||
<button type="submit"
|
||||
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" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Extend Deadline Modal -->
|
||||
<div id="extendDeadlineModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
|
||||
<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 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('extendDeadlineModal').classList.add('hidden')"></div>
|
||||
|
||||
<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">
|
||||
<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" %}
|
||||
</h3>
|
||||
|
||||
<form method="post" action="{% url 'agency_assignment_extend_deadline' assignment.slug %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-4">
|
||||
<label for="new_deadline" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
{% trans "New Deadline" %} <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<input type="datetime-local"
|
||||
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="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" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'agency_assignment_cancel' assignment.slug %}">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="cancel_reason" class="form-label fw-bold">
|
||||
<i class="fas fa-comment-alt me-2"></i>
|
||||
{% trans "Cancellation Reason" %}
|
||||
<span class="text-muted fw-normal">({% trans "Optional" %})</span>
|
||||
</label>
|
||||
<textarea class="form-control" id="cancel_reason" name="cancel_reason" rows="4"
|
||||
placeholder="{% trans 'Enter reason for cancelling this assignment (optional)...' %}"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="{% url 'agency_assignment_detail' assignment.slug %}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i>
|
||||
<div class="flex justify-end gap-3">
|
||||
<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" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="fas fa-times-circle me-1"></i>
|
||||
{% trans "Cancel Assignment" %}
|
||||
</button>
|
||||
<button type="submit"
|
||||
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>
|
||||
</div>
|
||||
</form>
|
||||
@ -479,107 +407,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Extend Deadline Modal -->
|
||||
<div class="modal fade" id="extendDeadlineModal" tabindex="-1">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">
|
||||
<i class="fas fa-clock me-2"></i>
|
||||
{% trans "Extend Assignment Deadline" %}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<form method="post" action="{% url 'agency_assignment_extend_deadline' assignment.slug %}">
|
||||
{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="new_deadline" class="form-label">
|
||||
{% trans "New Deadline" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="datetime-local" class="form-control" id="new_deadline"
|
||||
name="new_deadline" required>
|
||||
<small class="form-text text-muted">
|
||||
{% trans "Current deadline:" %} {{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
||||
</small>
|
||||
</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-clock me-1"></i> {% trans "Extend Deadline" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<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() {
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
|
||||
// Set minimum datetime for new deadline
|
||||
const deadlineInput = document.getElementById('new_deadline');
|
||||
if (deadlineInput) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,431 +1,385 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n widget_tweaks %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="form-container">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'agency_list' %}" class="text-decoration-none text-secondary">
|
||||
<i class="fas fa-building me-1"></i> {% trans "Agencies" %}
|
||||
</a>
|
||||
</li>
|
||||
{% if agency %}
|
||||
<li class="breadcrumb-item">
|
||||
<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 %}
|
||||
<li class="breadcrumb-item active" aria-current="page"
|
||||
style="
|
||||
color: #F43B5E; /* Rosy Accent Color */
|
||||
font-weight: 600;">{% trans "Create" %}</li>
|
||||
{% endif %}
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="space-y-6">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h6 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-building me-2"></i> {{ title }}
|
||||
</h6>
|
||||
<div class="d-flex gap-2">
|
||||
<!-- Header Card with Gradient Background -->
|
||||
<div class="bg-gradient-to-r from-temple-red to-[#7a1a29] rounded-2xl shadow-lg p-6 md:p-8 text-white">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl md:text-3xl font-bold mb-2 flex items-center gap-3">
|
||||
<i data-lucide="building" class="w-8 h-8"></i>
|
||||
{{ title }}
|
||||
</h1>
|
||||
<p class="text-white/80 text-sm md:text-base">
|
||||
{% if agency %}
|
||||
{% trans "Update agency information" %}
|
||||
{% else %}
|
||||
{% trans "Enter details to create a new agency." %}
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{% if agency %}
|
||||
<a href="{% url 'agency_detail' agency.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||
<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="btn btn-danger">
|
||||
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
||||
<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="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
||||
<a href="{% url 'agency_list' %}"
|
||||
class="inline-flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg text-white font-medium text-sm transition backdrop-blur-sm touch-target">
|
||||
<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>
|
||||
|
||||
{% 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 %}
|
||||
<p class="text-muted mb-0">{% trans "Contact" %}: {{ agency.contact_person }}</p>
|
||||
{% endif %}
|
||||
{% if agency.email %}
|
||||
<p class="text-muted mb-0">{{ agency.email }}</p>
|
||||
{% endif %}
|
||||
<small class="text-muted">
|
||||
{% trans "Created" %}: {{ agency.created_at|date:"d M Y" }} •
|
||||
{% trans "Last Updated" %}: {{ agency.updated_at|date:"d M Y" }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h5 class="alert-heading">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
||||
</h5>
|
||||
{% for error in form.non_field_errors %}
|
||||
<p class="mb-0">{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% if agency %}
|
||||
<!-- Current Agency Info Card -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||
<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>
|
||||
<div class="space-y-2">
|
||||
<h5 class="text-lg font-semibold text-gray-900">{{ agency.name }}</h5>
|
||||
{% if agency.contact_person %}
|
||||
<p class="text-gray-600 text-sm">{% trans "Contact" %}: {{ agency.contact_person }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if agency.email %}
|
||||
<p class="text-gray-600 text-sm">{{ agency.email }}</p>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" novalidate id="agency-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Name -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.name.id_for_label }}" class="form-label">
|
||||
{{ form.name.label }} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.name|add_class:"form-control" }}
|
||||
{% if form.name.errors %}
|
||||
{% for error in form.name.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.name.help_text %}
|
||||
<div class="form-text">{{ form.name.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Contact Person and Phone -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.contact_person.id_for_label }}" class="form-label">
|
||||
{{ form.contact_person.label }}
|
||||
</label>
|
||||
{{ form.contact_person|add_class:"form-control" }}
|
||||
{% if form.contact_person.errors %}
|
||||
{% for error in form.contact_person.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.contact_person.help_text %}
|
||||
<div class="form-text">{{ form.contact_person.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
||||
{{ form.phone.label }}
|
||||
</label>
|
||||
{{ form.phone|add_class:"form-control" }}
|
||||
{% if form.phone.errors %}
|
||||
{% for error in form.phone.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.phone.help_text %}
|
||||
<div class="form-text">{{ form.phone.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email and Website -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.email.id_for_label }}" class="form-label">
|
||||
{{ form.email.label }}<span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.email|add_class:"form-control" }}
|
||||
{% if form.email.errors %}
|
||||
{% for error in form.email.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.email.help_text %}
|
||||
<div class="form-text">{{ form.email.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.website.id_for_label }}" class="form-label">
|
||||
{{ form.website.label }}
|
||||
</label>
|
||||
{{ form.website|add_class:"form-control" }}
|
||||
{% if form.website.errors %}
|
||||
{% for error in form.website.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.website.help_text %}
|
||||
<div class="form-text">{{ form.website.help_text }}</div>
|
||||
{% 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 %}
|
||||
</div>
|
||||
|
||||
<!-- Country and City -->
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.country.id_for_label }}" class="form-label">
|
||||
{{ form.country.label }}
|
||||
</label>
|
||||
{{ form.country|add_class:"form-control" }}
|
||||
{% if form.country.errors %}
|
||||
{% for error in form.country.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.country.help_text %}
|
||||
<div class="form-text">{{ form.country.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="{{ form.city.id_for_label }}" class="form-label">
|
||||
{{ form.city.label }}
|
||||
</label>
|
||||
{{ form.city|add_class:"form-control" }}
|
||||
{% if form.city.errors %}
|
||||
{% for error in form.city.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.city.help_text %}
|
||||
<div class="form-text">{{ form.city.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div class="mb-4">
|
||||
<label for="{{ form.description.id_for_label }}" class="form-label">
|
||||
{{ form.description.label }}
|
||||
</label>
|
||||
{{ form.description|add_class:"form-control" }}
|
||||
{% if form.description.errors %}
|
||||
{% for error in form.description.errors %}
|
||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if form.description.help_text %}
|
||||
<div class="form-text">{{ form.description.help_text }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button form="agency-form" type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-save me-1"></i> {{ button_text }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="text-gray-500 text-xs">
|
||||
{% trans "Created" %}: {{ agency.created_at|date:"d M Y" }} •
|
||||
{% trans "Last Updated" %}: {{ agency.updated_at|date:"d M Y" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
|
||||
<div class="border-b border-gray-200 px-6 py-4 bg-gray-50 rounded-t-xl">
|
||||
<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 %}
|
||||
<div class="bg-red-50 border border-red-200 rounded-xl p-4 mb-6" role="alert">
|
||||
<div class="flex items-start gap-3">
|
||||
<i data-lucide="alert-circle" class="w-5 h-5 text-red-600 shrink-0 mt-0.5"></i>
|
||||
<div>
|
||||
<h5 class="font-bold text-red-800 mb-1">{% trans "Error" %}</h5>
|
||||
{% for error in form.non_field_errors %}
|
||||
<p class="text-red-700 text-sm">{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" novalidate id="agency-form" class="space-y-6">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Two Column Form Layout -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<!-- Name -->
|
||||
<div class="space-y-2">
|
||||
<label for="{{ form.name.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||
{{ form.name.label }} <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<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 %}
|
||||
{% for error in form.name.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.name.help_text %}
|
||||
<p class="text-gray-500 text-xs">{{ form.name.help_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Contact Person -->
|
||||
<div class="space-y-2">
|
||||
<label for="{{ form.contact_person.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||
{{ form.contact_person.label }}
|
||||
</label>
|
||||
<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 %}
|
||||
{% for error in form.contact_person.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.contact_person.help_text %}
|
||||
<p class="text-gray-500 text-xs">{{ form.contact_person.help_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Phone -->
|
||||
<div class="space-y-2">
|
||||
<label for="{{ form.phone.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||
{{ form.phone.label }}
|
||||
</label>
|
||||
<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 %}
|
||||
{% for error in form.phone.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.phone.help_text %}
|
||||
<p class="text-gray-500 text-xs">{{ form.phone.help_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Email -->
|
||||
<div class="space-y-2">
|
||||
<label for="{{ form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||
{{ form.email.label }} <span class="text-red-600">*</span>
|
||||
</label>
|
||||
<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 %}
|
||||
{% for error in form.email.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.email.help_text %}
|
||||
<p class="text-gray-500 text-xs">{{ form.email.help_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Website -->
|
||||
<div class="space-y-2">
|
||||
<label for="{{ form.website.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||
{{ form.website.label }}
|
||||
</label>
|
||||
<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 %}
|
||||
{% for error in form.website.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.website.help_text %}
|
||||
<p class="text-gray-500 text-xs">{{ form.website.help_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Country -->
|
||||
<div class="space-y-2">
|
||||
<label for="{{ form.country.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||
{{ form.country.label }}
|
||||
</label>
|
||||
<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 %}
|
||||
{% for error in form.country.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.country.help_text %}
|
||||
<p class="text-gray-500 text-xs">{{ form.country.help_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- City -->
|
||||
<div class="space-y-2">
|
||||
<label for="{{ form.city.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||
{{ form.city.label }}
|
||||
</label>
|
||||
<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 %}
|
||||
{% for error in form.city.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.city.help_text %}
|
||||
<p class="text-gray-500 text-xs">{{ form.city.help_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address (Full Width) -->
|
||||
<div class="space-y-2">
|
||||
<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 }}
|
||||
</label>
|
||||
<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 %}
|
||||
{% for error in form.description.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.description.help_text %}
|
||||
<p class="text-gray-500 text-xs">{{ form.description.help_text }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="pt-4 border-t border-gray-200">
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Reinitialize Lucide icons for dynamically added content
|
||||
lucide.createIcons();
|
||||
|
||||
// Form Validation
|
||||
const form = document.getElementById('agency-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
submitBtn.classList.add('loading');
|
||||
submitBtn.classList.add('opacity-75', 'cursor-not-allowed');
|
||||
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
|
||||
const name = document.getElementById('id_name');
|
||||
const name = document.getElementById('{{ form.name.id_for_label }}');
|
||||
if (name && !name.value.trim()) {
|
||||
e.preventDefault();
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`;
|
||||
lucide.createIcons();
|
||||
alert('{% trans "Agency name is required." %}');
|
||||
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())) {
|
||||
e.preventDefault();
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`;
|
||||
lucide.createIcons();
|
||||
alert('{% trans "Please enter a valid email address." %}');
|
||||
return;
|
||||
}
|
||||
|
||||
const website = document.getElementById('id_website');
|
||||
const website = document.getElementById('{{ form.website.id_for_label }}');
|
||||
if (website && website.value.trim() && !isValidURL(website.value.trim())) {
|
||||
e.preventDefault();
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`;
|
||||
lucide.createIcons();
|
||||
alert('{% trans "Please enter a valid website URL." %}');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3,694 +3,307 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- 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>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-briefcase 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="briefcase" class="w-8 h-8 text-temple-red"></i>
|
||||
</div>
|
||||
{{ assignment.job.title }}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
{% trans "Assignment Details" %} - {{ assignment.agency.name }}
|
||||
</p>
|
||||
<p class="text-gray-600">{% trans "Assignment Details" %} - {{ assignment.agency.name }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'agency_portal_dashboard' %}" class="btn btn-outline-secondary btn-sm me-2">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Dashboard" %}
|
||||
<div class="flex gap-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 data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Dashboard" %}
|
||||
</a>
|
||||
<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 data-lucide="user-plus" class="w-4 h-4"></i> {% trans "Submit New application" %}
|
||||
</a>
|
||||
<a href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="btn btn-sm btn-main-action {% if assignment.is_full %}disabled{% endif %}" >
|
||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit New application" %}
|
||||
</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 class="row">
|
||||
<!-- Assignment Overview -->
|
||||
<div class="col-lg-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<!-- Main Content -->
|
||||
<div class="lg:col-span-2 space-y-6">
|
||||
<!-- Assignment Details Card -->
|
||||
<div class="kaauh-card p-4 mb-4">
|
||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
{% trans "Assignment Details" %}
|
||||
</h5>
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<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" %}
|
||||
</h5>
|
||||
|
||||
<div class="row">
|
||||
<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 class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<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 class="mb-3">
|
||||
<label class="text-muted small">{% trans "Status" %}</label>
|
||||
<div>
|
||||
<span class="status-badge status-{{ assignment.status }}" >
|
||||
{{ assignment.get_status_display }}
|
||||
</span>
|
||||
</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 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<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>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Deadline" %}</label>
|
||||
<div class="{% if assignment.is_expired %}text-red-600{% else %}text-gray-700{% endif %}">
|
||||
<i data-lucide="calendar" class="w-4 h-4 inline mr-1"></i>
|
||||
{{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
||||
</div>
|
||||
{% if assignment.is_expired %}
|
||||
<small class="text-danger">
|
||||
<i class="fas fa-exclamation-triangle me-1"></i>{% trans "Expired" %}
|
||||
</small>
|
||||
<div class="text-sm text-red-600 mt-1">
|
||||
<i data-lucide="alert-triangle" class="w-3 h-3 inline mr-1"></i>{% trans "Expired" %}
|
||||
</div>
|
||||
{% else %}
|
||||
<small class="text-primary-theme">
|
||||
<i class="fas fa-clock me-1"></i>{{ assignment.days_remaining }} {% trans "days remaining" %}
|
||||
</small>
|
||||
<div class="text-sm text-blue-600 mt-1">
|
||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>{{ assignment.days_remaining }} {% trans "days remaining" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">{% trans "Maximum applications" %}</label>
|
||||
<div class="fw-bold">{{max_applications }} {% trans "applications" %}</div>
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Maximum applications" %}</label>
|
||||
<div class="font-bold text-gray-900">{{max_applications }} {% trans "applications" %}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if assignment.job.description %}
|
||||
<div class="mt-3 pt-3 border-top">
|
||||
<label class="text-muted small">{% trans "Job Description " %}</label>
|
||||
<div class="text-muted">
|
||||
{{ assignment.job.description|safe|truncatewords:50 }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</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 %}
|
||||
{% if assignment.job.description %}
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-2">{% trans "Job Description" %}</label>
|
||||
<div class="text-gray-700">{{ assignment.job.description|safe|truncatewords:50 }}</div>
|
||||
</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>
|
||||
|
||||
<!-- Submitted applications --> {% endcomment %}
|
||||
<div class="kaauh-card p-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-users me-2"></i>
|
||||
{% trans "Submitted applications" %} ({{ total_applications }})
|
||||
</h5>
|
||||
<span class="badge bg-primary-theme">{{ total_applications }}/{{ max_applications }}</span>
|
||||
</div>
|
||||
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Contact" %}</th>
|
||||
<th>{% trans "Stage" %}</th>
|
||||
<th>{% trans "Submitted" %}</th>
|
||||
<th>{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for application in page_obj %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold">{{ application.name }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="small">
|
||||
<div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div>
|
||||
<div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="small text-muted">
|
||||
{{ application.created_at|date:"Y-m-d H:i" }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'applicant_application_detail' application.slug %}" class="btn btn-sm btn-outline-primary" title="{% trans 'View Profile' %}">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Submitted applications -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<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 }})
|
||||
</h5>
|
||||
<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>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<nav aria-label="Candidate pagination">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if page_obj %}
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="border-b border-gray-200">
|
||||
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Name" %}</th>
|
||||
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Contact" %}</th>
|
||||
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Stage" %}</th>
|
||||
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Submitted" %}</th>
|
||||
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for application in page_obj %}
|
||||
<tr class="border-b border-gray-100 hover:bg-gray-50 transition">
|
||||
<td class="py-3 px-4">
|
||||
<div class="font-semibold text-gray-900">{{ application.name }}</div>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<div class="text-sm text-gray-600">
|
||||
<div class="flex items-center gap-1">
|
||||
<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>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<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 class="py-3 px-4">
|
||||
<div class="text-sm text-gray-500">
|
||||
{{ application.created_at|date:"Y-m-d H:i" }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-3 px-4">
|
||||
<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 data-lucide="eye" class="w-4 h-4"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<div class="flex justify-center items-center gap-2 mt-6">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<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">
|
||||
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == num %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ num }}</span>
|
||||
</li>
|
||||
<span class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-temple-red text-white font-semibold">{{ num }}</span>
|
||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
|
||||
</li>
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<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">
|
||||
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center py-8">
|
||||
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<i data-lucide="users" class="w-8 h-8 text-gray-400"></i>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-users fa-2x text-muted mb-3"></i>
|
||||
<h6 class="text-muted">{% trans "No applications submitted yet" %}</h6>
|
||||
<p class="text-muted small">
|
||||
{% trans "Submit applications using the form above to get started." %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
</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="lg:col-span-1 space-y-6">
|
||||
<!-- Progress Card -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<h5 class="text-xl font-bold text-gray-900 mb-4 text-center">{% trans "Submission Progress" %}</h5>
|
||||
|
||||
<div class="text-center mb-3">
|
||||
<div class="progress-ring">
|
||||
<svg width="120" height="120">
|
||||
<circle class="progress-ring-circle"
|
||||
stroke="#e9ecef"
|
||||
stroke-width="8"
|
||||
fill="transparent"
|
||||
r="52"
|
||||
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 }};"/>
|
||||
</svg>
|
||||
<div class="progress-ring-text">
|
||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||
{{ progress|floatformat:0 }}%
|
||||
<div class="text-center mb-4">
|
||||
<div class="relative w-32 h-32 mx-auto">
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" class="transform -rotate-90">
|
||||
<circle cx="64" cy="64" r="56" stroke="#e5e7eb" stroke-width="8" fill="none"/>
|
||||
<circle cx="64" cy="64" r="56" stroke="#9d2235" stroke-width="8" fill="none"
|
||||
style="stroke-dasharray: 351.86; stroke-dashoffset: {{ stroke_dashoffset }};"/>
|
||||
</svg>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||
<span class="text-3xl font-bold text-gray-900">{{ progress|floatformat:0 }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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="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>
|
||||
|
||||
<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 %}
|
||||
<span class="badge bg-primary-theme">{% trans "Can Submit" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">{% trans "Cannot Submit" %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assignment Info Card -->
|
||||
<div class="col kaauh-card p-4">
|
||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-info me-2"></i>
|
||||
{% trans "Assignment Info" %}
|
||||
</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">{% trans "Assigned Date" %}</label>
|
||||
<div class="fw-bold">{{ assignment.assigned_date|date:"Y-m-d" }}</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">{% trans "Days Remaining" %}</label>
|
||||
<div class="fw-bold {% if assignment.days_remaining <= 3 %}text-danger{% endif %}">
|
||||
{{ assignment.days_remaining }} {% trans "days" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">{% trans "Submission Rate" %}</label>
|
||||
<div class="fw-bold">
|
||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||
{{ progress|floatformat:1 }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<!-- Recent Messages Section -->
|
||||
{% if message_page_obj %}
|
||||
<div class="kaauh-card p-4 mt-4">
|
||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-comments me-2"></i>
|
||||
{% trans "Recent Messages" %}
|
||||
</h5>
|
||||
|
||||
<div class="row">
|
||||
{% for message in message_page_obj|slice:":6" %}
|
||||
<div class="col-lg-6 mb-3">
|
||||
<div class="message-item {% if not message.is_read %}unread{% endif %}">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<div class="fw-bold">{{ message.subject }}</div>
|
||||
<small class="text-muted">{{ message.created_at|date:"Y-m-d H:i" }}</small>
|
||||
</div>
|
||||
<div class="small text-muted mb-2">
|
||||
{% trans "From" %}: {{ message.sender.get_full_name }}
|
||||
</div>
|
||||
<div class="small">{{ message.message|truncatewords:30 }}</div>
|
||||
{% if not message.is_read %}
|
||||
<span class="badge bg-info mt-2">{% trans "New" %}</span>
|
||||
<div class="text-center">
|
||||
{% if assignment.can_submit %}
|
||||
<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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if message_page_obj.count > 6 %}
|
||||
<div class="text-center mt-3">
|
||||
<a href="#" class="btn btn-outline-primary btn-sm">
|
||||
{% trans "View All Messages" %}
|
||||
</a>
|
||||
<!-- Assignment Info Card -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<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" %}
|
||||
</h5>
|
||||
|
||||
<div class="space-y-4">
|
||||
<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>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Days Remaining" %}</label>
|
||||
<div class="font-bold {% if assignment.days_remaining <= 3 %}text-red-600{% endif %} text-gray-900">
|
||||
{{ assignment.days_remaining }} {% trans "days" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Submission Rate" %}</label>
|
||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||
<div class="font-bold text-gray-900">{{ progress|floatformat:1 }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Messages Section -->
|
||||
{% if message_page_obj %}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<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" %}
|
||||
</h5>
|
||||
|
||||
<div class="space-y-3">
|
||||
{% for message in message_page_obj|slice:":6" %}
|
||||
<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="flex justify-between items-start mb-2">
|
||||
<div class="font-semibold text-gray-900">{{ message.subject }}</div>
|
||||
<div class="text-xs text-gray-500">{{ message.created_at|date:"Y-m-d H:i" }}</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-600 mb-2">
|
||||
{% trans "From" %}: {{ message.sender.get_full_name }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-700">{{ message.message|truncatewords:30 }}</div>
|
||||
{% if not message.is_read %}
|
||||
<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 %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if message_page_obj.count > 6 %}
|
||||
<div class="text-center mt-4">
|
||||
<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" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</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 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>
|
||||
// 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() {
|
||||
const firstNameField = document.getElementById('first_name');
|
||||
if (firstNameField) {
|
||||
firstNameField.focus();
|
||||
}
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,230 +1,215 @@
|
||||
{% extends 'portal_base.html' %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Agency Dashboard" %} -{% trans "Aagency 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;
|
||||
}
|
||||
{% block title %}{% trans "Agency Dashboard" %} - {% trans "Agency Portal" %}{% endblock %}
|
||||
|
||||
.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 %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="px-2 py-2">
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-tachometer-alt me-2"></i>
|
||||
{% trans "Agency Dashboard" %}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
{% trans "Welcome back" %}, {{ agency.name }}!
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
{% comment %} <a href="{% url 'agency_portal_submit_application' %}" class="btn btn-main-action me-2">
|
||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %}
|
||||
</a>
|
||||
<div class="space-y-6">
|
||||
|
||||
>
|
||||
{% endif %}
|
||||
</a> {% endcomment %}
|
||||
<!-- Header Section -->
|
||||
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white rounded-2xl shadow-lg p-6 md:p-8">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||
<div>
|
||||
<div class="flex items-center gap-3 mb-2">
|
||||
<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 }}!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Overview Statistics -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-3 mb-2">
|
||||
<div class="kaauh-card shadow-sm h-100">
|
||||
<div class="card-body text-center px-2 py-2">
|
||||
<div class="text-primary-theme mb-2">
|
||||
<i class="fas fa-briefcase fa-2x"></i>
|
||||
</div>
|
||||
<h4 class="card-title">{{ total_assignments }}</h4>
|
||||
<p class="card-text text-muted">{% trans "Total Assignments" %}</p>
|
||||
<!-- Statistics Cards -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<!-- Total Assignments -->
|
||||
<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="briefcase" class="w-6 h-6"></i>
|
||||
<span class="text-sm font-semibold">{% trans "Total Assignments" %}</span>
|
||||
</div>
|
||||
</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-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 class="p-5 text-center">
|
||||
<div class="text-3xl font-bold text-temple-red mb-1">{{ total_assignments }}</div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wide">{% trans "Assigned Jobs" %}</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-primary-theme mb-2">
|
||||
<i class="fas fa-users fa-2x"></i>
|
||||
</div>
|
||||
<h4 class="card-title">{{ total_applications }}</h4>
|
||||
<p class="card-text text-muted">{% trans "Total Applications" %}</p>
|
||||
|
||||
<!-- Active Assignments -->
|
||||
<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="check-circle" class="w-6 h-6"></i>
|
||||
<span class="text-sm font-semibold">{% trans "Active Assignments" %}</span>
|
||||
</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>
|
||||
<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 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>
|
||||
|
||||
<!-- Job Assignments List -->
|
||||
<div class="kaauh-card shadow-sm px-3 py-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-tasks me-2"></i>
|
||||
{% trans "Your Job Assignments" %}
|
||||
</h5>
|
||||
<span class="badge bg-secondary">{{ assignment_stats|length }} {% trans "assignments" %}</span>
|
||||
<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-5">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-3">
|
||||
<i data-lucide="list-todo" class="w-6 h-6"></i>
|
||||
<h2 class="text-lg font-bold">{% trans "Your Job Assignments" %}</h2>
|
||||
</div>
|
||||
<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 class="p-6">
|
||||
{% 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 %}
|
||||
<div class="col-lg-6 col-xl-4 mb-4">
|
||||
<div class="card h-100 border-0 shadow-sm assignment-card">
|
||||
<div class="card-body">
|
||||
<!-- Assignment Header -->
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="card-title mb-1">
|
||||
<div class="border border-gray-200 rounded-xl overflow-hidden hover:border-temple-red transition-all duration-200 hover:shadow-lg">
|
||||
<!-- Assignment Header -->
|
||||
<div class="bg-temple-cream border-b border-gray-200 p-4">
|
||||
<div class="flex justify-between items-start gap-2 mb-2">
|
||||
<div class="flex-1">
|
||||
<h5 class="font-bold text-temple-dark mb-1">
|
||||
<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 }}
|
||||
</a>
|
||||
</h6>
|
||||
<p class="text-muted small mb-2">
|
||||
<i class="fas fa-building me-1"></i>
|
||||
{{ stats.assignment.job.department }}
|
||||
</h5>
|
||||
<p class="text-sm text-gray-600">
|
||||
<i data-lucide="building-2" class="w-3 h-3 inline mr-1"></i>
|
||||
{{ stats.assignment.job.department|default:"N/A" }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="shrink-0">
|
||||
{% 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' %}
|
||||
<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' %}
|
||||
<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 %}
|
||||
<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 %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assignment Details -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Deadline" %}</small>
|
||||
<strong class="{% if stats.days_remaining <= 3 %}text-danger{% elif stats.days_remaining <= 7 %}text-warning{% else %}text-success{% endif %}">
|
||||
<!-- Assignment Body -->
|
||||
<div class="p-4 space-y-3">
|
||||
<!-- Deadline & Applications -->
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<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" }}
|
||||
{% if stats.days_remaining >= 0 %}
|
||||
({{ stats.days_remaining }} {% trans "days left" %})
|
||||
{% else %}
|
||||
({{ stats.days_remaining }} {% trans "days overdue" %})
|
||||
{% endif %}
|
||||
</strong>
|
||||
<div class="text-xs text-gray-500 font-normal">
|
||||
{% if stats.days_remaining >= 0 %}
|
||||
({{ stats.days_remaining }} {% trans "days left" %})
|
||||
{% else %}
|
||||
({{ stats.days_remaining }} {% trans "days overdue" %})
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="text-muted d-block">{% trans "Applications" %}</small>
|
||||
<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>
|
||||
|
||||
<!-- Progress Bar -->
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<small class="text-muted">{% trans "Submission Progress" %}</small>
|
||||
<small class="text-muted">{{ stats.application_count }}/{{ stats.assignment.max_candidates }}</small>
|
||||
<div>
|
||||
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
||||
<span>{% trans "Submission Progress" %}</span>
|
||||
<span>{{ stats.application_count }}/{{ stats.assignment.max_candidates }}</span>
|
||||
</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 %}
|
||||
<div class="progress-bar {% if progress >= 90 %}bg-danger{% elif progress >= 70 %}bg-warning{% else %}bg-success{% endif %}"
|
||||
style="width: {{ progress|floatformat:0 }}%"></div>
|
||||
{% widthratio progress stats.assignment.max_candidates 100 as percentage %}
|
||||
<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 %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
{% if stats.can_submit %}
|
||||
<div class="flex gap-2 pt-2 border-t border-gray-100">
|
||||
{% if stats.can_submit %}
|
||||
<a href="{% url 'agency_portal_submit_application_page' stats.assignment.slug %}"
|
||||
class="btn btn-sm btn-main-action">
|
||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %}
|
||||
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 data-lucide="user-plus" class="w-4 h-4"></i>
|
||||
{% trans "Submit" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-secondary" disabled>
|
||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submissions Closed" %}
|
||||
{% else %}
|
||||
<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 data-lucide="x" class="w-4 h-4"></i>
|
||||
{% trans "Closed" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
|
||||
class="btn btn-sm btn-main-action">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||
</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>
|
||||
{% endif %}
|
||||
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
|
||||
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 data-lucide="eye" class="w-4 h-4"></i>
|
||||
{% trans "View" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% include "includes/paginator.html" %}
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-briefcase fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">{% trans "No Job Assignments Found" %}</h5>
|
||||
<p class="text-muted">
|
||||
<div class="text-center py-12">
|
||||
<i data-lucide="briefcase" class="w-20 h-20 text-gray-300 mx-auto mb-4"></i>
|
||||
<h5 class="text-xl font-bold text-gray-900 mb-2">{% trans "No Job Assignments Found" %}</h5>
|
||||
<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." %}
|
||||
</p>
|
||||
</div>
|
||||
@ -234,24 +219,16 @@
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.assignment-card {
|
||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
.card-hover {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.assignment-card:hover {
|
||||
.card-hover:hover {
|
||||
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 {
|
||||
color: var(--kaauh-teal-dark) !important;
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
.progress-bar-animated {
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
@ -259,11 +236,13 @@
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Auto-refresh for unread messages count
|
||||
setInterval(function() {
|
||||
// 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();
|
||||
}, 300000); // 5 minutes
|
||||
});
|
||||
|
||||
@ -1,335 +1,245 @@
|
||||
{% extends 'portal_base.html' %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Agency Portal Login" %} - 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;
|
||||
}
|
||||
|
||||
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 title %}{% trans "Agency Portal Login" %} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<!-- Login Header -->
|
||||
<div class="login-header">
|
||||
<div class="mb-3">
|
||||
<i class="fas fa-building fa-3x"></i>
|
||||
<body class="bg-gradient-to-br from-temple-red to-[#7a1a29] min-h-screen">
|
||||
<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 -->
|
||||
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-8 rounded-t-2xl text-center">
|
||||
<div class="mb-4 inline-flex items-center justify-center w-20 h-20 rounded-full bg-white/20 backdrop-blur-sm">
|
||||
<i data-lucide="building" class="w-10 h-10"></i>
|
||||
</div>
|
||||
<h2 class="text-2xl md:text-3xl font-bold mb-2">{% trans "Agency Portal" %}</h2>
|
||||
<p class="text-white/80 text-sm md:text-base">
|
||||
{% trans "Submit candidates for job assignments" %}
|
||||
</p>
|
||||
</div>
|
||||
<h3 class="mb-2">{% trans "Agency Portal" %}</h3>
|
||||
<p class="mb-0 opacity-75">
|
||||
{% trans "Submit candidates for job assignments" %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Login Body -->
|
||||
<div class="login-body">
|
||||
<!-- Messages -->
|
||||
<!-- Login Body -->
|
||||
<div class="p-8 md:p-10">
|
||||
|
||||
<!-- Login Form -->
|
||||
<form method="post" novalidate id="login-form" class="space-y-6">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Login Form -->
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Access Token Field -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.token.id_for_label }}" class="form-label fw-bold">
|
||||
<i class="fas fa-key me-2"></i>
|
||||
{% trans "Access Token" %}
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="fas fa-lock"></i>
|
||||
</span>
|
||||
{{ form.token }}
|
||||
</div>
|
||||
{% if form.token.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{% for error in form.token.errors %}{{ error }}{% endfor %}
|
||||
<!-- Access Token Field -->
|
||||
<div class="space-y-2">
|
||||
<label for="{{ form.token.id_for_label }}" class="block text-sm font-bold text-gray-700">
|
||||
<i data-lucide="key" class="w-4 h-4 inline mr-2"></i>
|
||||
{% trans "Access Token" %}
|
||||
</label>
|
||||
<div class="relative">
|
||||
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-temple-red">
|
||||
<i data-lucide="lock" class="w-5 h-5"></i>
|
||||
</span>
|
||||
<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>
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">
|
||||
{% trans "Enter the access token provided by the hiring organization" %}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div class="mb-4">
|
||||
<label for="{{ form.password.id_for_label }}" class="form-label fw-bold">
|
||||
<i class="fas fa-shield-alt me-2"></i>
|
||||
{% trans "Password" %}
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="fas fa-key"></i>
|
||||
</span>
|
||||
{{ form.password }}
|
||||
{% if form.token.errors %}
|
||||
<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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-xs text-gray-500">
|
||||
{% trans "Enter the access token provided by the hiring organization" %}
|
||||
</p>
|
||||
</div>
|
||||
{% if form.password.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
{% for error in form.password.errors %}{{ error }}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<small class="form-text text-muted">
|
||||
{% trans "Enter the password for this access token" %}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-login btn-lg">
|
||||
<i class="fas fa-sign-in-alt me-2"></i>
|
||||
<!-- Password Field -->
|
||||
<div class="space-y-2">
|
||||
<label for="{{ form.password.id_for_label }}" class="block text-sm font-bold text-gray-700">
|
||||
<i data-lucide="shield-check" class="w-4 h-4 inline mr-2"></i>
|
||||
{% trans "Password" %}
|
||||
</label>
|
||||
<div class="relative">
|
||||
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-temple-red">
|
||||
<i data-lucide="key" class="w-5 h-5"></i>
|
||||
</span>
|
||||
<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>
|
||||
{% if form.password.errors %}
|
||||
<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 %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-xs text-gray-500">
|
||||
{% trans "Enter the password for this access token" %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<button type="submit"
|
||||
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 data-lucide="log-in" class="w-5 h-5"></i>
|
||||
{% trans "Access Portal" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
<!-- Information Section -->
|
||||
<div class="info-section">
|
||||
<h6 class="fw-bold mb-3" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
{% trans "Need Help?" %}
|
||||
</h6>
|
||||
<!-- Information Section -->
|
||||
<div class="mt-8 bg-temple-cream rounded-xl p-6 border border-gray-200">
|
||||
<h5 class="text-temple-dark font-bold mb-4 flex items-center gap-2">
|
||||
<i data-lucide="help-circle" class="w-5 h-5 text-temple-red"></i>
|
||||
{% trans "Need Help?" %}
|
||||
</h5>
|
||||
|
||||
<div class="row text-center">
|
||||
<div class="col-6 mb-3">
|
||||
<div class="feature-icon mx-auto">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-center">
|
||||
<div>
|
||||
<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 data-lucide="mail" class="w-8 h-8"></i>
|
||||
</div>
|
||||
<h6 class="font-bold text-gray-900 mb-1">{% trans "Contact Support" %}</h6>
|
||||
<p class="text-sm text-gray-500">
|
||||
{% trans "Reach out to your hiring contact" %}
|
||||
</p>
|
||||
</div>
|
||||
<h6 class="fw-bold">{% trans "Contact Support" %}</h6>
|
||||
<small class="text-muted">
|
||||
{% trans "Reach out to your hiring contact" %}
|
||||
</small>
|
||||
</div>
|
||||
<div class="col-6 mb-3">
|
||||
<div class="feature-icon mx-auto">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
<div>
|
||||
<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 data-lucide="book-open" class="w-8 h-8"></i>
|
||||
</div>
|
||||
<h6 class="font-bold text-gray-900 mb-1">{% trans "Documentation" %}</h6>
|
||||
<p class="text-sm text-gray-500">
|
||||
{% trans "View user guides and tutorials" %}
|
||||
</p>
|
||||
</div>
|
||||
<h6 class="fw-bold">{% trans "Documentation" %}</h6>
|
||||
<small class="text-muted">
|
||||
{% trans "View user guides and tutorials" %}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security Notice -->
|
||||
<div class="alert alert-info mt-3 mb-0">
|
||||
<h6 class="alert-heading">
|
||||
<i class="fas fa-shield-alt me-2"></i>
|
||||
{% trans "Security Notice" %}
|
||||
</h6>
|
||||
<p class="mb-2 small">
|
||||
{% trans "This portal is for authorized agency partners only. Access is monitored and logged." %}
|
||||
</p>
|
||||
<hr>
|
||||
<p class="mb-0 small">
|
||||
{% trans "If you believe you've received this link in error, please contact the hiring organization immediately." %}
|
||||
</p>
|
||||
<!-- Security Notice -->
|
||||
<div class="mt-6 bg-blue-50 border border-blue-200 rounded-xl p-5">
|
||||
<h6 class="text-blue-800 font-bold mb-3 flex items-center gap-2">
|
||||
<i data-lucide="shield-check" class="w-4 h-4"></i>
|
||||
{% trans "Security Notice" %}
|
||||
</h6>
|
||||
<p class="text-blue-700 text-sm mb-3">
|
||||
{% trans "This portal is for authorized agency partners only. Access is monitored and logged." %}
|
||||
</p>
|
||||
<hr class="border-blue-200 mb-3">
|
||||
<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." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Toast Notification Container -->
|
||||
<div id="toast-container"></div>
|
||||
</body>
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Focus on access token field
|
||||
const accessTokenField = document.getElementById('{{ form.token.id_for_label }}');
|
||||
if (accessTokenField) {
|
||||
accessTokenField.focus();
|
||||
}
|
||||
lucide.createIcons();
|
||||
|
||||
// Auto-format access token (remove spaces and convert to uppercase)
|
||||
const accessTokenInput = document.getElementById('{{ form.token.id_for_label }}');
|
||||
if (accessTokenInput) {
|
||||
accessTokenInput.addEventListener('input', function(e) {
|
||||
// Remove spaces and convert to uppercase
|
||||
this.value = this.value.replace(/\s+/g, '');
|
||||
});
|
||||
}
|
||||
// Auto-format access token (remove spaces and convert to uppercase)
|
||||
const accessTokenInput = document.getElementById('{{ form.token.id_for_label }}');
|
||||
if (accessTokenInput) {
|
||||
accessTokenInput.addEventListener('input', function() {
|
||||
// Remove spaces and convert to uppercase
|
||||
this.value = this.value.replace(/\s+/g, '').toUpperCase();
|
||||
});
|
||||
|
||||
// Show/hide password functionality
|
||||
// Focus on load
|
||||
accessTokenInput.focus();
|
||||
}
|
||||
|
||||
// Toggle password visibility
|
||||
function togglePassword() {
|
||||
const passwordField = document.getElementById('{{ form.password.id_for_label }}');
|
||||
const passwordToggle = document.createElement('button');
|
||||
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';
|
||||
const toggleBtn = document.getElementById('password-toggle');
|
||||
|
||||
if (passwordField && passwordField.parentElement) {
|
||||
passwordField.parentElement.style.position = 'relative';
|
||||
if (passwordField && toggleBtn) {
|
||||
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
passwordField.setAttribute('type', type);
|
||||
|
||||
passwordToggle.addEventListener('click', function() {
|
||||
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||
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
|
||||
const form = document.querySelector('form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const accessToken = accessTokenInput.value.trim();
|
||||
const password = passwordField.value.trim();
|
||||
// Form validation
|
||||
const form = document.getElementById('login-form');
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const accessToken = accessTokenInput.value.trim();
|
||||
const passwordField = document.getElementById('{{ form.password.id_for_label }}');
|
||||
const password = passwordField ? passwordField.value.trim() : '';
|
||||
|
||||
if (!accessToken) {
|
||||
e.preventDefault();
|
||||
showError('{% trans "Please enter your access token." %}');
|
||||
accessTokenInput.focus();
|
||||
return;
|
||||
}
|
||||
if (!accessToken) {
|
||||
e.preventDefault();
|
||||
showError('{% trans "Please enter your access token." %}');
|
||||
accessTokenInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password) {
|
||||
e.preventDefault();
|
||||
showError('{% trans "Please enter your password." %}');
|
||||
passwordField.focus();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!password) {
|
||||
e.preventDefault();
|
||||
showError('{% trans "Please enter your password." %}');
|
||||
passwordField.focus();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showError(message) {
|
||||
// Remove existing alerts
|
||||
const existingAlerts = document.querySelectorAll('.alert-danger');
|
||||
existingAlerts.forEach(alert => alert.remove());
|
||||
function showError(message) {
|
||||
// Remove existing toasts
|
||||
const container = document.getElementById('toast-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
// Create new alert
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
|
||||
alertDiv.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
`;
|
||||
// Create new toast
|
||||
const toast = document.createElement('div');
|
||||
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';
|
||||
toast.innerHTML = `
|
||||
<i data-lucide="alert-circle" class="w-5 h-5"></i>
|
||||
<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
|
||||
const loginBody = document.querySelector('.login-body');
|
||||
loginBody.insertBefore(alertDiv, loginBody.firstChild);
|
||||
container.appendChild(toast);
|
||||
lucide.createIcons();
|
||||
|
||||
// Auto-dismiss after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (alertDiv.parentNode) {
|
||||
alertDiv.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
// Auto-dismiss after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.classList.add('animate-fade-out');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
</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 %}
|
||||
@ -2,232 +2,126 @@
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4 persons-list">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="px-2 py-2">
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-users me-2"></i>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6 px-2 py-2">
|
||||
<div>
|
||||
<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" %}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
{% trans "All applicants who come through" %} {{ agency.name }}
|
||||
</p>
|
||||
<p class="text-gray-600">{% trans "All applicants who come through" %} {{ agency.name }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<!-- Add Person Button -->
|
||||
<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>
|
||||
</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">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add New Applicant" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter Section -->
|
||||
<div class="kaauh-card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" class="search-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="search" class="form-label fw-semibold">
|
||||
<i class="fas fa-search me-1"></i>{% trans "Search" %}
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||
<div class="p-6">
|
||||
<form method="get">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="search" class="block text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
||||
<i data-lucide="search" class="w-4 h-4"></i>{% trans "Search" %}
|
||||
</label>
|
||||
<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"
|
||||
name="q"
|
||||
value="{{ search_query }}"
|
||||
placeholder="{% trans 'Search by name, email, phone...' %}">
|
||||
</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>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Summary -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-6">
|
||||
<div class="kaauh-card shadow-sm h-100 p-3">
|
||||
<div class="card-body text-center">
|
||||
<div class="text-primary-theme mb-2">
|
||||
<i class="fas fa-users fa-2x"></i>
|
||||
</div>
|
||||
<h4 class="card-title">{{ total_persons }}</h4>
|
||||
<p class="card-text text-muted">{% trans "Total Persons" %}</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||
<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="users" class="w-6 h-6 text-temple-red"></i>
|
||||
</div>
|
||||
<h4 class="text-2xl font-bold text-gray-900 mb-1">{{ total_persons }}</h4>
|
||||
<p class="text-gray-600">{% trans "Total Persons" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="kaauh-card shadow-sm h-100 p-3">
|
||||
<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 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>
|
||||
<h4 class="text-2xl font-bold text-gray-900 mb-1">{{ page_obj|length }}</h4>
|
||||
<p class="text-gray-600">{% trans "Showing on this page" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Persons Table -->
|
||||
<div class="kaauh-card shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive person-table">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Email" %}</th>
|
||||
<th scope="col">{% trans "Phone" %}</th>
|
||||
<th scope="col">{% trans "Created At" %}</th>
|
||||
{% comment %} <th scope="col">{% trans "Stage" %}</th>
|
||||
<th scope="col">{% trans "Applied Date" %}</th> {% endcomment %}
|
||||
<th scope="col" class="text-center">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for person in page_obj %}
|
||||
<tr class="person-row">
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-primary-theme text-white d-flex align-items-center justify-content-center me-2"
|
||||
style="width: 32px; height: 32px; font-size: 14px; font-weight: 600;">
|
||||
{{ person.first_name|first|upper }}{{ person.last_name|first|upper }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-semibold">{{ person.first_name }} {{ person.last_name }}</div>
|
||||
{% if person.address %}
|
||||
<small class="text-muted">{{ person.address|truncatechars:50 }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<a href="mailto:{{ person.email }}" class="text-decoration-none text-dark">
|
||||
{{ person.email }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ person.phone|default:"-" }}</td>
|
||||
{% comment %} <td>
|
||||
<span class="badge bg-light text-dark">
|
||||
{{ 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"
|
||||
hx-get="{% url 'person_update' person.slug %}"
|
||||
hx-target="#updateModalBody"
|
||||
hx-swap="outerrHTML"
|
||||
hx-select="#person-form"
|
||||
hx-vals='{"view":"portal"}'
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
title="{% trans 'Edit Person' %}"
|
||||
>
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<table class="w-full">
|
||||
<thead class="bg-gray-50 border-b border-gray-200">
|
||||
<tr>
|
||||
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Name" %}</th>
|
||||
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Email" %}</th>
|
||||
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Phone" %}</th>
|
||||
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Created At" %}</th>
|
||||
<th class="text-center py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for person in page_obj %}
|
||||
<tr class="border-b border-gray-100 hover:bg-gray-50 transition cursor-pointer">
|
||||
<td class="py-4 px-4">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center font-bold text-sm">
|
||||
{{ person.first_name|first|upper }}{{ person.last_name|first|upper }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold text-gray-900">{{ person.first_name }} {{ person.last_name }}</div>
|
||||
{% if person.address %}
|
||||
<div class="text-sm text-gray-500">{{ person.address|truncatechars:50 }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="py-4 px-4">
|
||||
<a href="mailto:{{ person.email }}" class="text-gray-900 hover:text-temple-red transition">{{ person.email }}</a>
|
||||
</td>
|
||||
<td class="py-4 px-4">{{ person.phone|default:"-" }}</td>
|
||||
<td class="py-4 px-4">{{ person.created_at|date:"d-m-Y" }}</td>
|
||||
<td class="py-4 px-4 text-center">
|
||||
<button type="button" data-bs-toggle="modal" data-bs-target="#updateModal"
|
||||
hx-get="{% url 'person_update' person.slug %}"
|
||||
hx-target="#updateModalBody"
|
||||
hx-swap="outerrHTML"
|
||||
hx-select="#person-form"
|
||||
hx-vals='{"view":"portal"}'
|
||||
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' %}">
|
||||
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-users fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">{% trans "No persons found" %}</h5>
|
||||
<p class="text-muted">
|
||||
<div class="text-center py-10">
|
||||
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<i data-lucide="users" class="w-8 h-8 text-gray-400"></i>
|
||||
</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 %}
|
||||
{% trans "Try adjusting your search or filter criteria." %}
|
||||
{% else %}
|
||||
@ -236,8 +130,8 @@
|
||||
</p>
|
||||
{% if not search_query and not stage_filter and agency.assignments.exists %}
|
||||
<a href="{% url 'agency_portal_submit_application_page' agency.assignments.first.slug %}"
|
||||
class="btn btn-main-action">
|
||||
<i class="fas fa-user-plus me-1"></i> {% trans "Add First Person" %}
|
||||
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="user-plus" class="w-4 h-4"></i> {% trans "Add First Person" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -247,46 +141,34 @@
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<nav aria-label="{% trans 'Persons pagination' %}" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
<nav aria-label="{% trans 'Persons pagination' %}" class="mt-6">
|
||||
<div class="flex justify-center items-center gap-2">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
||||
<i class="fas fa-angle-double-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<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>
|
||||
</li>
|
||||
<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">
|
||||
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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">
|
||||
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == num %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ num }}</span>
|
||||
</li>
|
||||
<span class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-temple-red text-white font-semibold">{{ num }}</span>
|
||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">{{ num }}</a>
|
||||
</li>
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
||||
<i class="fas fa-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<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>
|
||||
</li>
|
||||
<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">
|
||||
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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">
|
||||
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -303,79 +185,77 @@
|
||||
<div class="modal-body" id="updateModalBody">
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="d-flex gap-2">
|
||||
<button form="person-form" type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-save me-1"></i> {% trans "Update" %}
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<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 data-lucide="save" class="w-4 h-4"></i> {% trans "Update" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Person Modal -->
|
||||
<div class="modal fade modal-lg" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal fade" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="personModalLabel">
|
||||
<i class="fas fa-users me-2"></i>
|
||||
<h5 class="modal-title flex items-center gap-2" id="personModalLabel">
|
||||
<i data-lucide="users" class="w-5 h-5"></i>
|
||||
{% trans "Applicant Details" %}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="personModalBody">
|
||||
<form id="person_form" method="post" action="{% url 'person_create' %}" >
|
||||
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="view" value="portal">
|
||||
<input type="hidden" name="agency" value="{{ agency.slug }}">
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
{{ person_form.first_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div>
|
||||
{{ person_form.middle_name|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{{ person_form.last_name|as_crispy_field }}
|
||||
<div>
|
||||
{{ person_form.last_name|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.email|as_crispy_field }}
|
||||
{{person_form.errors}}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
{{ person_form.email|as_crispy_field }}
|
||||
{{person_form.errors}}
|
||||
</div>
|
||||
<div>
|
||||
{{ person_form.phone|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.phone|as_crispy_field }}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
{{ person_form.gpa|as_crispy_field }}
|
||||
</div>
|
||||
<div>
|
||||
{{ person_form.national_id|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.gpa|as_crispy_field }}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
{{ person_form.date_of_birth|as_crispy_field }}
|
||||
</div>
|
||||
<div>
|
||||
{{ person_form.nationality|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.national_id|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.date_of_birth|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.nationality|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
<div>
|
||||
{{ person_form.address|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-main-action" type="submit" form="person_form">{% trans "Save" %}</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||
<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="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" %}
|
||||
</button>
|
||||
</div>
|
||||
@ -389,36 +269,13 @@ function openPersonModal(personId, personName) {
|
||||
document.getElementById('person-modal-text').innerHTML = `<strong>${personName}</strong> (ID: ${personId})`;
|
||||
modal.show();
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
|
||||
function editPerson(personId) {
|
||||
// Placeholder for edit functionality
|
||||
// This would typically open a modal or navigate to edit page
|
||||
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.getElementById('stage').addEventListener('change', function() {
|
||||
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();
|
||||
}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@ -11,7 +11,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="px-4 py-6">
|
||||
|
||||
{# Header #}
|
||||
<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">
|
||||
<img src="{% if applicant.user.profile_image %}{{ applicant.user.profile_image.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}"
|
||||
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">
|
||||
<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>
|
||||
@ -39,19 +39,19 @@
|
||||
{# Tab Navigation #}
|
||||
<div class="border-b border-gray-200 overflow-x-auto">
|
||||
<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')"
|
||||
data-tab="profile-details">
|
||||
<i data-lucide="user-circle" class="w-4 h-4 mr-2 inline"></i>
|
||||
{% trans "Profile Details" %}
|
||||
</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')"
|
||||
data-tab="applications-history">
|
||||
<i data-lucide="list-alt" class="w-4 h-4 mr-2 inline"></i>
|
||||
{% trans "My Applications" %}
|
||||
</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')"
|
||||
data-tab="document-management">
|
||||
<i data-lucide="file-upload" class="w-4 h-4 mr-2 inline"></i>
|
||||
@ -69,20 +69,20 @@
|
||||
<!-- Basic Information Section -->
|
||||
<div class="mb-8">
|
||||
<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" %}
|
||||
</h4>
|
||||
<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 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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.first_name|default:"" }}</span>
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.last_name|default:"" }}</span>
|
||||
@ -90,7 +90,7 @@
|
||||
{% 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 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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.middle_name }}</span>
|
||||
@ -98,7 +98,7 @@
|
||||
{% endif %}
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.email|default:"" }}</span>
|
||||
@ -109,13 +109,13 @@
|
||||
<!-- Contact Information Section -->
|
||||
<div class="mb-8">
|
||||
<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" %}
|
||||
</h4>
|
||||
<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 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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.phone|default:"" }}</span>
|
||||
@ -123,7 +123,7 @@
|
||||
{% 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 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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium text-right max-w-xs">{{ applicant.address|linebreaksbr }}</span>
|
||||
@ -132,10 +132,10 @@
|
||||
{% 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 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>
|
||||
</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>
|
||||
</a>
|
||||
</div>
|
||||
@ -146,27 +146,27 @@
|
||||
<!-- Personal Details Section -->
|
||||
<div class="mb-8">
|
||||
<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" %}
|
||||
</h4>
|
||||
<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 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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.date_of_birth|date:"M d, Y"|default:"" }}</span>
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.get_gender_display|default:"" }}</span>
|
||||
</div>
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.get_nationality_display|default:"" }}</span>
|
||||
@ -177,14 +177,14 @@
|
||||
<!-- Professional Information Section -->
|
||||
<div class="mb-8">
|
||||
<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" %}
|
||||
</h4>
|
||||
<div class="bg-gray-50 rounded-xl p-4 space-y-3">
|
||||
{% 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 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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.user.designation }}</span>
|
||||
@ -193,7 +193,7 @@
|
||||
{% 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 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>
|
||||
</div>
|
||||
<span class="text-gray-900 font-medium">{{ applicant.gpa }}</span>
|
||||
@ -206,20 +206,20 @@
|
||||
{# Applications History Tab #}
|
||||
<div id="applications-history" class="tab-content hidden">
|
||||
<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" %}
|
||||
</h4>
|
||||
|
||||
{% if applications %}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{% 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">
|
||||
<!-- Job Title -->
|
||||
<div class="mb-3">
|
||||
<h5 class="font-bold text-gray-900 mb-2">
|
||||
<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 }}
|
||||
</a>
|
||||
</h5>
|
||||
@ -233,7 +233,7 @@
|
||||
<div class="space-y-2 mb-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<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 }}
|
||||
</span>
|
||||
</div>
|
||||
@ -250,7 +250,7 @@
|
||||
<!-- Action Button -->
|
||||
<div class="mt-auto">
|
||||
<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>
|
||||
{% trans "View Details" %}
|
||||
</a>
|
||||
@ -260,10 +260,10 @@
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="bg-kaauh-blue/5 border-2 border-dashed border-kaauh-blue/30 rounded-2xl p-8 text-center">
|
||||
<i data-lucide="info" class="w-12 h-12 text-kaauh-blue mx-auto mb-4"></i>
|
||||
<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-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>
|
||||
<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>
|
||||
</a>
|
||||
</div>
|
||||
@ -273,7 +273,7 @@
|
||||
{# Document Management Tab #}
|
||||
<div id="document-management" class="tab-content hidden">
|
||||
<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" %}
|
||||
</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." %}
|
||||
</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')">
|
||||
<i data-lucide="upload-cloud" class="w-4 h-4"></i>
|
||||
{% trans "Upload New Document" %}
|
||||
@ -296,14 +296,14 @@
|
||||
id="document-{{ document.id }}">
|
||||
<div class="flex-1">
|
||||
<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>
|
||||
</div>
|
||||
<span class="text-xs text-gray-500">({{ document.file.name|split:"/"|last }})</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<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>
|
||||
</a>
|
||||
<button hx-post="{% url 'document_delete' document.pk %}"
|
||||
@ -356,7 +356,7 @@
|
||||
|
||||
// Remove active state from all tabs
|
||||
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');
|
||||
});
|
||||
|
||||
@ -365,7 +365,7 @@
|
||||
|
||||
// Add active state to selected tab
|
||||
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');
|
||||
}
|
||||
|
||||
|
||||
@ -4,177 +4,21 @@
|
||||
{% block title %}{% trans "Create Account" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* MODERN KAAUH DESIGN SYSTEM */
|
||||
/* ---------------------------------------------------------------------- */
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-teal-light: #f0f7f8;
|
||||
--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 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">
|
||||
<!-- Header -->
|
||||
<div class="bg-gradient-to-r from-temple-red to-[#7a1a29] text-white p-8 text-center">
|
||||
<h2 class="text-2xl font-bold flex items-center justify-center gap-3">
|
||||
<i data-lucide="user-plus" class="w-7 h-7"></i>
|
||||
{% trans "Create Account" %}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="kaauh-body">
|
||||
<!-- Body -->
|
||||
<div class="p-10">
|
||||
{% if 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 }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
@ -183,83 +27,83 @@
|
||||
<form method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-grid">
|
||||
<div class="field-group grid-4">
|
||||
<label class="field-label">{% trans "First Name" %} <span class="required">*</span></label>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "First Name" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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 class="field-group grid-4">
|
||||
<label class="field-label">{% trans "Middle Name" %}</label>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "Middle Name" %}</label>
|
||||
{{ 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 class="field-group grid-4">
|
||||
<label class="field-label">{% trans "Last Name" %} <span class="required">*</span></label>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "Last Name" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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 class="field-group grid-8">
|
||||
<label class="field-label">{% trans "Email Address" %} <span class="required">*</span></label>
|
||||
<div class="md:col-span-2 space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "Email Address" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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 class="field-group grid-4">
|
||||
<label class="field-label">{% trans "Phone Number" %} <span class="required">*</span></label>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "Phone Number" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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 class="field-group grid-6">
|
||||
<label class="field-label">{% trans "GPA" %} <span class="required">*</span></label>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "GPA" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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 class="field-group grid-6">
|
||||
<label class="field-label">{% trans "National ID / Iqama" %} <span class="required">*</span></label>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "National ID / Iqama" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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 class="field-group grid-6">
|
||||
<label class="field-label">{% trans "Nationality" %} <span class="required">*</span></label>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "Nationality" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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 class="field-group grid-6">
|
||||
<label class="field-label">{% trans "Gender" %} <span class="required">*</span></label>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "Gender" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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 class="field-group grid-6">
|
||||
<label class="field-label">{% trans "Password" %} <span class="required">*</span></label>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "Password" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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 class="field-group grid-6">
|
||||
<label class="field-label">{% trans "Confirm Password" %} <span class="required">*</span></label>
|
||||
<div class="space-y-2">
|
||||
<label class="block text-sm font-bold text-gray-900">{% trans "Confirm Password" %} <span class="text-red-500">*</span></label>
|
||||
{{ 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>
|
||||
|
||||
{% if form.non_field_errors %}
|
||||
<div class="grid-12">
|
||||
<div class="alert alert-danger">
|
||||
<div class="md:col-span-3">
|
||||
<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 %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="grid-12">
|
||||
<button type="submit" class="btn-submit">
|
||||
<div class="md:col-span-3">
|
||||
<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" %}
|
||||
</button>
|
||||
</div>
|
||||
@ -267,12 +111,39 @@
|
||||
</form>
|
||||
</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?" %}
|
||||
<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" %}
|
||||
</a>
|
||||
</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 %}
|
||||
@ -3,202 +3,277 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4">
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="candidate-header-card">
|
||||
<div class="d-flex justify-content-between align-items-start flex-wrap">
|
||||
<div class="flex-grow-1">
|
||||
<h1 class="h3 mb-1">
|
||||
<i class="fas fa-user-plus"></i>
|
||||
{% trans "Create New Application" %}
|
||||
</h1>
|
||||
<p class="text-white opacity-75 mb-0">{% trans "Enter details to create a new application record." %}</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2 mt-1">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#personModal">
|
||||
<i class="fas fa-user-plus me-1"></i>
|
||||
<span class="d-none d-sm-inline">{% trans "Create New Applicant" %}</span>
|
||||
</button>
|
||||
<a href="{% url 'application_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="px-4 py-6">
|
||||
<!-- Header Card -->
|
||||
<div class="bg-gradient-to-br from-temple-red to-red-800 rounded-xl shadow-xl p-6 mb-6 text-white">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||
<div class="flex-1">
|
||||
<h1 class="text-3xl font-bold mb-2 flex items-center gap-2">
|
||||
<i data-lucide="user-plus" class="w-8 h-8"></i>
|
||||
{% trans "Create New Application" %}
|
||||
</h1>
|
||||
<p class="text-red-100 text-lg">{% trans "Enter details to create a new application record." %}</p>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button type="button" class="modal-trigger bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2" data-modal="personModal">
|
||||
<i data-lucide="user-plus" class="w-4 h-4"></i>
|
||||
<span class="hidden sm:inline">{% trans "Create New Applicant" %}</span>
|
||||
</button>
|
||||
<a href="{% url 'application_list' %}" class="bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2" title="{% trans 'Back to List' %}">
|
||||
<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>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h2 class="h5 mb-0 text-primary">
|
||||
<i class="fas fa-file-alt me-1"></i>
|
||||
<!-- Form Card -->
|
||||
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-gray-200">
|
||||
<div class="px-6 py-4 border-b border-gray-200 bg-gray-50">
|
||||
<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" %}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="p-6">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
{# Split form into two columns for better horizontal use #}
|
||||
<div class="row g-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{% for field in form %}
|
||||
<div class="col-md-6">
|
||||
{{ field|as_crispy_field }}
|
||||
<div>
|
||||
<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>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<hr class="mt-4 mb-4">
|
||||
<button class="btn btn-main-action" type="submit">
|
||||
<i class="fas fa-save me-1"></i>
|
||||
{% trans "Create Application" %}
|
||||
</button>
|
||||
<div class="border-t border-gray-200 mt-6 pt-6">
|
||||
<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 data-lucide="save" class="w-5 h-5"></i>
|
||||
{% trans "Create Application" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade modal-lg" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="personModalLabel">
|
||||
<i class="fas fa-question-circle me-2"></i>{% trans "Help" %}
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="hidden fixed inset-0 z-50 overflow-y-auto" id="personModal" role="dialog" aria-labelledby="personModalLabel">
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div class="fixed inset-0 bg-black/50 transition-opacity" aria-hidden="true"></div>
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen sm:align-middle" aria-hidden="true">​</span>
|
||||
<div class="inline-block align-bottom bg-white rounded-2xl text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-3xl sm:w-full">
|
||||
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 border-b border-gray-200 flex justify-between items-center">
|
||||
<h3 class="text-lg font-semibold text-gray-900 flex items-center gap-2" id="personModalLabel">
|
||||
<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 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">
|
||||
{% csrf_token %}
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
{{ person_form.first_name|as_crispy_field }}
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<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 class="col-md-4">
|
||||
{{ person_form.middle_name|as_crispy_field }}
|
||||
<div>
|
||||
<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>
|
||||
<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 class="col-md-4">
|
||||
{{ person_form.last_name|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.email|as_crispy_field }}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<div>
|
||||
<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>
|
||||
<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 class="col-md-6">
|
||||
{{ person_form.phone|as_crispy_field }}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<div>
|
||||
<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>
|
||||
<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 class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.gpa|as_crispy_field }}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||
<div>
|
||||
<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>
|
||||
<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 class="col-md-6">
|
||||
{{ person_form.national_id|as_crispy_field }}
|
||||
<div class="mt-4">
|
||||
<label for="{{ person_form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
{{ person_form.address.label }}
|
||||
</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>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.date_of_birth|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.nationality|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-12">
|
||||
{{ person_form.address|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-main-action" data-bs-dismiss="modal" form="person_form">{% trans "Save" %}</button>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
||||
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||
<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">
|
||||
<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>
|
||||
{% 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 %}
|
||||
@ -3,269 +3,99 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<h1 class="text-3xl font-bold text-temple-red mb-2 flex items-center gap-3">
|
||||
<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" %}
|
||||
</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." %}
|
||||
</p>
|
||||
</div>
|
||||
{# Assuming application_detail URL takes object.pk or object.slug #}
|
||||
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Application" %}
|
||||
<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">
|
||||
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Application" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="warning-section">
|
||||
<div class="warning-icon">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<div class="flex justify-center">
|
||||
<div class="w-full max-w-4xl">
|
||||
<!-- 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="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>
|
||||
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||
<p class="warning-text">
|
||||
<h3 class="text-2xl font-bold text-amber-800 mb-3">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||
<p class="text-amber-700">
|
||||
{% 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 %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card kaauh-card mb-4">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-file-alt me-2"></i>
|
||||
<!-- Application Info Card -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||
<div class="bg-white border-b border-gray-200 p-5">
|
||||
<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" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="app-info">
|
||||
<div class="p-6">
|
||||
<div class="bg-gray-50 rounded-xl p-6 mb-6">
|
||||
{% if object.candidate %}
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
{# Assuming candidate has a profile_image field #}
|
||||
<div class="flex items-center gap-4 mb-5 pb-5 border-b border-gray-200">
|
||||
{% 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 %}
|
||||
<div class="avatar-placeholder me-3">
|
||||
<i class="fas fa-user text-muted fa-2x"></i>
|
||||
<div class="w-20 h-20 rounded-full bg-gray-200 flex items-center justify-center border-3 border-temple-red">
|
||||
<i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<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 %}
|
||||
<p class="text-muted mb-0">{{ object.candidate.email }}</p>
|
||||
<p class="text-gray-600">{{ object.candidate.email }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if object.job %}
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-briefcase"></i>
|
||||
<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="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||
<i data-lucide="briefcase" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Job Title" %}</div>
|
||||
<div class="info-value">{{ object.job.title }}</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Job Title" %}</div>
|
||||
<div class="text-gray-900 text-base">{{ object.job.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
<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="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||
<i data-lucide="calendar" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Applied On" %}</div>
|
||||
<div class="info-value">{{ object.created_at|date:"F d, Y \a\t P" }}</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Applied On" %}</div>
|
||||
<div class="text-gray-900 text-base">{{ object.created_at|date:"F d, Y \a\t P" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object.status %}
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-cogs"></i>
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||
<i data-lucide="settings" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Current Status" %}</div>
|
||||
<div class="info-value">{{ object.get_status_display }}</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Current Status" %}</div>
|
||||
<div class="text-gray-900 text-base">{{ object.get_status_display }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -273,63 +103,62 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card kaauh-card mb-4">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-list me-2"></i>
|
||||
<!-- Consequences Card -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||
<div class="bg-white border-b border-gray-200 p-5">
|
||||
<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?" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="consequence-list">
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<div class="p-6">
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<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." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<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." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<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." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<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." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<li class="flex items-start gap-2 text-gray-700">
|
||||
<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." %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card kaauh-card">
|
||||
<div class="card-body">
|
||||
<!-- Confirmation Form -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<form method="post" id="deleteForm">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required>
|
||||
<label class="form-check-label" for="confirm_delete">
|
||||
<div class="mb-6">
|
||||
<label class="flex items-start gap-3 cursor-pointer">
|
||||
<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">
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary btn-lg">
|
||||
<i class="fas fa-times me-2"></i>
|
||||
<div class="flex flex-col sm:flex-row gap-3 justify-between">
|
||||
<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 data-lucide="x" class="w-5 h-5"></i>
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="btn btn-danger btn-lg"
|
||||
id="deleteButton"
|
||||
disabled>
|
||||
<i class="fas fa-trash me-2"></i>
|
||||
<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">
|
||||
<i data-lucide="trash-2" class="w-5 h-5"></i>
|
||||
{% trans "Delete Application Permanently" %}
|
||||
</button>
|
||||
</div>
|
||||
@ -349,47 +178,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function validateForm() {
|
||||
const checkboxChecked = confirmDeleteCheckbox.checked;
|
||||
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);
|
||||
// Initialize state on page load
|
||||
validateForm();
|
||||
|
||||
// Add confirmation and prevent double submission before final submission
|
||||
deleteForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const candidateName = "{{ object.candidate.full_name|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 the 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 application by CANDIDATE_PLACEHOLDER for JOB_PLACEHOLDER? This action cannot be reversed!{% endblocktrans %}";
|
||||
|
||||
const confirmationMessage = confirmationMessageTemplate
|
||||
.replace('CANDIDATE_PLACEHOLDER', candidateName)
|
||||
.replace('JOB_PLACEHOLDER', jobTitle);
|
||||
|
||||
if (confirm(confirmationMessage)) {
|
||||
// Disable button and show loading state
|
||||
deleteButton.disabled = true;
|
||||
deleteButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>{% trans "Deleting..." %}';
|
||||
|
||||
// Submit the form programmatically
|
||||
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..." %}';
|
||||
deleteForm.submit();
|
||||
} else {
|
||||
// If the user cancels the dialog, ensure the button state is reset/validated
|
||||
validateForm();
|
||||
}
|
||||
});
|
||||
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -3,211 +3,99 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
|
||||
{# Breadcrumb #}
|
||||
<nav class="mb-6" aria-label="breadcrumb">
|
||||
<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>
|
||||
<div class="px-4 py-6" id="application-detail-content">
|
||||
<nav aria-label="breadcrumb" class="mb-6">
|
||||
<ol class="flex items-center space-x-2 text-sm">
|
||||
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition">Home</a></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">
|
||||
{% trans "Job:" %} ({{ application.job.title }})
|
||||
</a></li>
|
||||
<li><a href="{% url 'job_detail' application.job.slug %}" class="text-gray-500 hover:text-temple-red transition">Job: {{application.job.title}}</a></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>
|
||||
</nav>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||
|
||||
{# LEFT COLUMN: MAIN DETAILS AND TABS #}
|
||||
<div class="lg:col-span-8">
|
||||
<div class="detail-card bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{# LEFT COLUMN: MAIN application DETAILS AND TABS #}
|
||||
<div class="lg:col-span-2">
|
||||
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-gray-200">
|
||||
|
||||
{# HEADER SECTION #}
|
||||
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-6">
|
||||
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
||||
<div class="flex-1">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold mb-2">{{ application.name }}</h1>
|
||||
<div class="bg-gradient-to-br from-temple-red to-red-800 text-white p-6">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-extrabold mb-2">{{ application.name }}</h1>
|
||||
<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">
|
||||
{% trans "Stage:" %} <span class="font-normal">{{ application.stage }}</span>
|
||||
<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-bold">{{ application.stage }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm text-white/80">
|
||||
{% trans "Applied for:" %} <strong class="text-white">{{ application.job.title }}</strong>
|
||||
<p class="text-red-100 text-sm">
|
||||
{% trans "Applied for:" %} <strong>{{ application.job.title }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{# Change Stage button #}
|
||||
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
|
||||
<button type="button"
|
||||
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"
|
||||
onclick="document.getElementById('stageUpdateModal').classList.remove('hidden')">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||
{% trans "Change Stage" %}
|
||||
<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">
|
||||
<i data-lucide="repeat" class="w-4 h-4"></i> {% trans "Change Stage" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# TABS NAVIGATION #}
|
||||
<div class="border-b border-gray-200 bg-gray-50 overflow-x-auto">
|
||||
<div class="flex min-w-max px-4 gap-1">
|
||||
<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('contact-pane')"
|
||||
data-tab="contact-pane">
|
||||
<i data-lucide="id-card" class="w-4 h-4 mr-2 inline"></i>
|
||||
{% trans "Contact & Job" %}
|
||||
</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"
|
||||
onclick="showTab('timeline-pane')"
|
||||
data-tab="timeline-pane">
|
||||
<i data-lucide="route" class="w-4 h-4 mr-2 inline"></i>
|
||||
{% trans "Journey Timeline" %}
|
||||
</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"
|
||||
onclick="showTab('documents-pane')"
|
||||
data-tab="documents-pane">
|
||||
<i data-lucide="file-text" class="w-4 h-4 mr-2 inline"></i>
|
||||
{% trans "Documents" %}
|
||||
</button>
|
||||
{% if application.parsed_summary %}
|
||||
<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('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>
|
||||
{# LEFT TABS NAVIGATION #}
|
||||
<div class="bg-gray-50 border-b border-gray-200 px-6">
|
||||
<ul class="flex space-x-1" id="candidateTabs" role="tablist">
|
||||
<li class="role-presentation">
|
||||
<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">
|
||||
<i data-lucide="id-card" class="w-4 h-4"></i> {% trans "Contact & Job" %}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="role-presentation">
|
||||
<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">
|
||||
<i data-lucide="route" class="w-4 h-4"></i> {% trans "Journey Timeline" %}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="role-presentation">
|
||||
<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">
|
||||
<i data-lucide="file-text" class="w-4 h-4"></i> {% trans "Documents" %}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="tab-content" id="candidateTabsContent">
|
||||
|
||||
{# TAB 1: CONTACT & DATES #}
|
||||
<div id="contact-pane" class="tab-content">
|
||||
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
|
||||
<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="info-card bg-gray-50 rounded-xl p-4 border border-gray-200">
|
||||
<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="mail" class="w-6 h-6 text-temple-red"></i>
|
||||
</div>
|
||||
{# TAB 1 CONTENT: CONTACT & DATES #}
|
||||
<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">{% trans "Core Details" %}</h5>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
|
||||
<i data-lucide="mail" class="w-8 h-8 text-gray-400 shrink-0"></i>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Email" %}</p>
|
||||
<p class="font-semibold text-gray-900">{{ application.email }}</p>
|
||||
<p class="text-xs text-gray-500">{% trans "Email" %}</p>
|
||||
<p class="font-semibold text-gray-800">{{ application.email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200">
|
||||
<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 class="flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
|
||||
<i data-lucide="briefcase" class="w-8 h-8 text-gray-400 shrink-0"></i>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Position Applied" %}</p>
|
||||
<p class="font-semibold text-gray-900">{{ application.job.title }}</p>
|
||||
<p class="text-xs text-gray-500">{% trans "Position Applied" %}</p>
|
||||
<p class="font-semibold text-gray-800">{{ application.job.title }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200 md:col-span-2">
|
||||
<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="calendar-check" class="w-6 h-6 text-temple-red"></i>
|
||||
</div>
|
||||
<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>
|
||||
<div class="md:col-span-2 flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
|
||||
<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">
|
||||
<p class="font-semibold text-gray-800">{{ application.created_at|date:"M d, Y H:i" }}</p>
|
||||
<span class="bg-gray-200 text-gray-700 px-2 py-1 rounded text-xs font-medium">
|
||||
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i>
|
||||
{{ application.created_at|naturaltime }}
|
||||
</span>
|
||||
</div>
|
||||
@ -215,372 +103,395 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# TAB 2: JOURNEY TIMELINE #}
|
||||
<div id="timeline-pane" class="tab-content hidden">
|
||||
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
||||
<div class="p-4 border-b border-gray-200">
|
||||
<h5 class="text-sm font-bold text-gray-600 flex items-center gap-2">
|
||||
<i data-lucide="route" class="w-5 h-5"></i>
|
||||
{% trans "Application Journey" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
|
||||
<p class="text-xs font-bold text-gray-500 uppercase tracking-wide mb-4">{% trans "Current Stage" %}</p>
|
||||
<div class="bg-temple-red/5 border border-temple-red/30 rounded-xl p-4 mb-6">
|
||||
<p class="text-xl font-bold text-temple-red mb-1">{{ application.stage }}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }}
|
||||
</p>
|
||||
{# TAB 2 CONTENT: TIMELINE #}
|
||||
<div class="tab-pane hidden" id="timeline-pane" role="tabpanel" aria-labelledby="timeline-tab">
|
||||
<div class="bg-white border border-gray-200 rounded-xl">
|
||||
<div class="p-4 border-b border-gray-200">
|
||||
<h5 class="text-sm font-semibold text-gray-600 flex items-center gap-2">
|
||||
<i data-lucide="route" class="w-4 h-4"></i>{% trans "Application Journey" %}
|
||||
</h5>
|
||||
</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>
|
||||
<div class="relative pl-8 border-l-2 border-gray-200 space-y-6">
|
||||
|
||||
{# Application Submitted #}
|
||||
<div class="timeline-item relative">
|
||||
<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-3 h-3"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Application Submitted" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
||||
{{ application.created_at|date:"M d, Y" }}
|
||||
<span class="mx-2">|</span>
|
||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
||||
{{ application.created_at|date:"h:i A" }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="p-6">
|
||||
<h6 class="text-xs uppercase tracking-wider text-gray-500 font-bold mb-3">{% trans "Current Stage" %}</h6>
|
||||
<div class="p-4 mb-4 rounded-lg bg-red-50 border border-red-200">
|
||||
<p class="font-bold text-lg text-temple-dark mb-1">{{ application.stage }}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
{% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{% if application.exam_date %}
|
||||
<div class="timeline-item relative">
|
||||
<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="clipboard-check" class="w-3 h-3"></i>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Exam" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
||||
{{ application.exam_date|date:"M d, Y" }}
|
||||
<span class="mx-2">|</span>
|
||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
||||
{{ application.exam_date|date:"h:i A" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<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">
|
||||
<div class="absolute left-3.5 top-0 bottom-0 w-0.5 bg-gray-200"></div>
|
||||
|
||||
{% if application.get_interview_date %}
|
||||
<div class="timeline-item relative">
|
||||
<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">
|
||||
<i data-lucide="message-circle" class="w-3 h-3"></i>
|
||||
<div class="relative mb-6">
|
||||
<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="file-signature" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div class="ml-12">
|
||||
<p class="font-semibold text-gray-800">{% trans "Application Submitted" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.created_at|date:"M d, Y" }}
|
||||
<span class="mx-2">|</span>
|
||||
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.created_at|date:"h:i A" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Interview" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
||||
{{ application.get_interview_date }}
|
||||
<span class="mx-2">|</span>
|
||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
||||
{{ application.get_interview_time }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if application.offer_date %}
|
||||
<div class="timeline-item relative">
|
||||
<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">
|
||||
<i data-lucide="handshake" class="w-3 h-3"></i>
|
||||
{% if application.exam_date %}
|
||||
<div class="relative mb-6">
|
||||
<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-4 h-4"></i>
|
||||
</div>
|
||||
<div class="ml-12">
|
||||
<p class="font-semibold text-gray-800">{% trans "Exam" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.exam_date|date:"M d, Y" }}
|
||||
<span class="mx-2">|</span>
|
||||
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.exam_date|date:"h:i A" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Offer" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
||||
{{ application.offer_date|date:"M d, Y" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if application.hired_date %}
|
||||
<div class="timeline-item relative">
|
||||
<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">
|
||||
<i data-lucide="user-check" class="w-3 h-3"></i>
|
||||
{% if application.get_interview_date %}
|
||||
<div class="relative mb-6">
|
||||
<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-4 h-4"></i>
|
||||
</div>
|
||||
<div class="ml-12">
|
||||
<p class="font-semibold text-gray-800">{% trans "Interview" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.get_interview_date}}
|
||||
<span class="mx-2">|</span>
|
||||
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.get_interview_time}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Hired" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
||||
{{ application.hired_date|date:"M d, Y" }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if application.offer_date %}
|
||||
<div class="relative mb-6">
|
||||
<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-4 h-4"></i>
|
||||
</div>
|
||||
<div class="ml-12">
|
||||
<p class="font-semibold text-gray-800">{% trans "Offer" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.offer_date|date:"M d, Y" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if application.hired_date %}
|
||||
<div class="relative mb-6">
|
||||
<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="check-circle" class="w-4 h-4"></i>
|
||||
</div>
|
||||
<div class="ml-12">
|
||||
<p class="font-semibold text-gray-800">{% trans "Hired" %}</p>
|
||||
<p class="text-xs text-gray-500">
|
||||
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.hired_date|date:"M d, Y" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# TAB 3: DOCUMENTS #}
|
||||
<div id="documents-pane" class="tab-content hidden">
|
||||
{% with documents=application.documents %}
|
||||
{% include 'includes/document_list.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
{# TAB 4: AI SUMMARY #}
|
||||
{% if application.parsed_summary %}
|
||||
<div id="summary-pane" class="tab-content hidden">
|
||||
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
|
||||
<i data-lucide="sparkles" class="w-5 h-5"></i>
|
||||
{% trans "AI Generated Summary" %}
|
||||
</h5>
|
||||
<div class="bg-gray-50 rounded-xl p-4 border-l-4 border-temple-red">
|
||||
{% include 'includes/application_modal_body.html' %}
|
||||
</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>
|
||||
|
||||
{# TAB 3 CONTENT: DOCUMENTS #}
|
||||
<div class="tab-pane hidden" id="documents-pane" role="tabpanel" aria-labelledby="documents-tab">
|
||||
{% with documents=application.documents %}
|
||||
{% include 'includes/document_list.html' %}
|
||||
{% 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" %}
|
||||
{# RIGHT COLUMN: ACTIONS AND TIMING #}
|
||||
<div class="space-y-6">
|
||||
{# ACTIONS CARD #}
|
||||
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-200">
|
||||
<h5 class="text-sm font-semibold text-gray-600 mb-4 flex items-center gap-2">
|
||||
<i data-lucide="settings" class="w-4 h-4"></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">
|
||||
<div class="space-y-3">
|
||||
<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">
|
||||
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||
{% trans "Back to List" %}
|
||||
</a>
|
||||
{% if application.resume %}
|
||||
<a href="{{ application.resume.url }}" download
|
||||
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>
|
||||
{% trans "Download Resume" %}
|
||||
</a>
|
||||
<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">
|
||||
<i data-lucide="download" class="w-4 h-4"></i>
|
||||
{% trans "Download Resume" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Time to Hire #}
|
||||
<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="clock" class="w-5 h-5"></i>
|
||||
{% trans "Time to Hire:" %}
|
||||
{# TIME TO HIRE CARD #}
|
||||
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-200">
|
||||
<h5 class="text-sm font-semibold text-gray-600 mb-4 flex items-center gap-2">
|
||||
<i data-lucide="clock" class="w-4 h-4"></i>{% trans "Time to Hire:" %}
|
||||
</h5>
|
||||
{% with days=application.time_to_hire_days %}
|
||||
<p class="text-2xl font-bold text-temple-red">
|
||||
{% if days > 0 %}{{ days }} day{{ days|pluralize }}{% else %}0{% endif %}
|
||||
</p>
|
||||
{% endwith %}
|
||||
</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">
|
||||
{% if application.is_resume_parsed %}
|
||||
{% include 'recruitment/application_resume_template.html' %}
|
||||
{% else %}
|
||||
{% if application.scoring_timeout %}
|
||||
<div class="flex flex-col items-center justify-center py-6">
|
||||
<div class="flex items-center gap-2 text-temple-red">
|
||||
<i data-lucide="bot" class="w-6 h-6 animate-pulse"></i>
|
||||
<span class="text-sm font-medium">{% trans "Resume Analysis In Progress..." %}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
{% with days=application.time_to_hire_days %}
|
||||
{% if days > 0 %}
|
||||
<p class="text-2xl font-bold text-temple-dark">{{ days }}</p>
|
||||
<p class="text-sm text-gray-500">day{{ days|pluralize }}</p>
|
||||
{% else %}
|
||||
<div class="flex flex-col items-center justify-center py-6">
|
||||
<button type="submit"
|
||||
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>
|
||||
{% trans "Unable to Parse Resume, Click to Retry" %}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-2xl font-bold text-temple-dark">0</p>
|
||||
<p class="text-sm text-gray-500">days</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Stage Update Modal for Staff Users #}
|
||||
{% 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 %}
|
||||
<div class="resume-parsed-section">
|
||||
{% if application.is_resume_parsed %}
|
||||
{% include 'recruitment/application_resume_template.html' %}
|
||||
{% else %}
|
||||
{% if application.scoring_timeout %}
|
||||
<div class="flex justify-center items-center py-12">
|
||||
<div class="flex items-center gap-3 text-temple-red">
|
||||
<i data-lucide="robot" class="w-8 h-8 animate-pulse"></i>
|
||||
<span class="text-sm">{% trans "Resume Analysis In Progress..." %}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="flex justify-center items-center py-12">
|
||||
<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>`">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||
{% trans "Unable to Parse Resume, click to retry" %}
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# STAGE UPDATE MODAL INCLUDED FOR STAFF USERS #}
|
||||
{% if user.is_staff %}
|
||||
{% include "recruitment/partials/stage_update_modal.html" with application=application form=stage_form %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
|
||||
// Tab switching functionality
|
||||
function showTab(tabId) {
|
||||
// Hide all tab contents
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.add('hidden');
|
||||
// ========================================
|
||||
// Tab Navigation Functionality
|
||||
// ========================================
|
||||
const tabButtons = document.querySelectorAll('.tab-btn');
|
||||
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');
|
||||
});
|
||||
|
||||
// Add active state to clicked button
|
||||
this.classList.add('active', 'text-temple-dark', 'border-b-temple-red', 'bg-white');
|
||||
this.classList.remove('text-gray-600', 'border-transparent');
|
||||
this.setAttribute('aria-selected', 'true');
|
||||
|
||||
// Hide all tab panes
|
||||
tabPanes.forEach(pane => {
|
||||
pane.classList.add('hidden');
|
||||
pane.classList.remove('block');
|
||||
});
|
||||
|
||||
// Show the target tab pane
|
||||
const targetPane = document.getElementById(targetTabId);
|
||||
if (targetPane) {
|
||||
targetPane.classList.remove('hidden');
|
||||
targetPane.classList.add('block');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Remove active state from all tabs
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
btn.classList.remove('border-temple-red', 'text-temple-red', 'bg-white');
|
||||
btn.classList.add('border-transparent', 'text-gray-600');
|
||||
// ========================================
|
||||
// Document Upload Modal Functionality
|
||||
// ========================================
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Show selected tab content
|
||||
document.getElementById(tabId).classList.remove('hidden');
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add active state to selected tab
|
||||
const activeBtn = document.querySelector(`[data-tab="${tabId}"]`);
|
||||
activeBtn.classList.add('active', 'border-temple-red', 'text-temple-red', 'bg-white');
|
||||
activeBtn.classList.remove('border-transparent', 'text-gray-600');
|
||||
// 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
|
||||
showTab('contact-pane');
|
||||
|
||||
// Modal close functionality
|
||||
document.addEventListener('click', function(e) {
|
||||
const modal = document.getElementById('stageUpdateModal');
|
||||
if (e.target === modal) {
|
||||
modal.classList.add('hidden');
|
||||
// Close modal on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && uploadModal && isModalOpen) {
|
||||
uploadModal.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
isModalOpen = false;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
// ========================================
|
||||
// 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>
|
||||
{% endblock %}
|
||||
File diff suppressed because it is too large
Load Diff
@ -3,267 +3,113 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="form-container">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'application_list' %}" class="text-decoration-none text-secondary">
|
||||
<i class="fas fa-users me-1"></i> {% trans "Applications" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'application_detail' object.slug %}" class="text-decoration-none text-secondary">
|
||||
{{ object.name }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page"
|
||||
style="
|
||||
color: #F43B5E; /* Rosy Accent Color */
|
||||
font-weight: 600;">{% trans "Update" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="px-4 py-6 max-w-4xl mx-auto">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
<nav aria-label="breadcrumb" class="mb-6">
|
||||
<ol class="flex items-center space-x-2 text-sm">
|
||||
<li>
|
||||
<a href="{% url 'application_list' %}" class="text-gray-500 hover:text-temple-red transition">
|
||||
<i data-lucide="users" class="w-4 h-4 inline mr-1"></i> {% trans "Applications" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="text-gray-400">/</li>
|
||||
<li>
|
||||
<a href="{% url 'application_detail' object.slug %}" class="text-gray-500 hover:text-temple-red transition">
|
||||
{{ object.name }}
|
||||
</a>
|
||||
</li>
|
||||
<li class="text-temple-red font-semibold" aria-current="page">{% trans "Update" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3 style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-user-edit me-2"></i> {% trans "Update Application" %}
|
||||
</h3>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'application_detail' object.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||
</a>
|
||||
<a href="{% url 'application_delete' object.slug %}" class="btn btn-danger">
|
||||
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
||||
</a>
|
||||
<a href="{% url 'application_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
||||
</a>
|
||||
</div>
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6">
|
||||
<h3 class="text-2xl font-bold text-temple-dark flex items-center gap-2">
|
||||
<i data-lucide="user-edit" class="w-6 h-6"></i>
|
||||
{% trans "Update Application" %}
|
||||
</h3>
|
||||
<div class="flex gap-2">
|
||||
<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 data-lucide="eye" class="w-4 h-4"></i>
|
||||
{% trans "View Details" %}
|
||||
</a>
|
||||
<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 data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
{% trans "Delete" %}
|
||||
</a>
|
||||
<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 data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||
{% trans "Back to List" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Profile 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">
|
||||
{% if object.profile_image %}
|
||||
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}"
|
||||
class="current-image">
|
||||
{% else %}
|
||||
<div class="current-image d-flex align-items-center justify-content-center bg-light">
|
||||
<i class="fas fa-user text-muted"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h5 class="mb-1">{{ object.name }}</h5>
|
||||
{% if object.email %}
|
||||
<p class="text-muted mb-0">{{ object.email }}</p>
|
||||
{% endif %}
|
||||
<small class="text-muted">
|
||||
{% trans "Created" %}: {{ object.created_at|date:"d M Y" }} •
|
||||
{% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }}
|
||||
</small>
|
||||
<!-- Current Profile Info -->
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200 mb-6">
|
||||
<div class="p-6">
|
||||
<div class="bg-gray-50 rounded-lg p-4">
|
||||
<h6 class="text-sm font-semibold text-temple-dark mb-3 flex items-center gap-2">
|
||||
<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 %}
|
||||
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}"
|
||||
class="w-24 h-24 rounded-full border-2 border-temple-red object-cover">
|
||||
{% else %}
|
||||
<div class="w-24 h-24 rounded-full border-2 border-gray-300 bg-gray-100 flex items-center justify-center">
|
||||
<i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h5 class="text-lg font-semibold text-gray-900 mb-1">{{ object.name }}</h5>
|
||||
{% if object.email %}
|
||||
<p class="text-gray-600 text-sm mb-1">{{ object.email }}</p>
|
||||
{% endif %}
|
||||
<p class="text-gray-500 text-xs">
|
||||
{% trans "Created" %}: {{ object.created_at|date:"d M Y" }} •
|
||||
{% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form Card -->
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h5 class="alert-heading">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
||||
</h5>
|
||||
{% for error in form.non_field_errors %}
|
||||
<p class="mb-0">{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
||||
</div>
|
||||
<!-- Form Card -->
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||
<div class="p-6">
|
||||
{% if form.non_field_errors %}
|
||||
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4" role="alert">
|
||||
<h5 class="font-semibold text-red-800 flex items-center gap-2 mb-2">
|
||||
<i data-lucide="alert-triangle" class="w-5 h-5"></i>
|
||||
{% trans "Error" %}
|
||||
</h5>
|
||||
{% for error in form.non_field_errors %}
|
||||
<p class="text-red-700 text-sm">{{ error }}</p>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'application_update' object.slug %}" enctype="multipart/form-data" id="candidate-form">
|
||||
{% csrf_token %}
|
||||
{{form|crispy}}
|
||||
</form>
|
||||
<div class="d-flex gap-2">
|
||||
<button form="candidate-form" type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-save me-1"></i> {% trans "Update" %}
|
||||
</button>
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<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 }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="{% url 'application_update' object.slug %}" enctype="multipart/form-data" id="candidate-form">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
</form>
|
||||
|
||||
<div class="flex gap-2 mt-6">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -273,6 +119,26 @@
|
||||
{% block customJS %}
|
||||
<script>
|
||||
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
|
||||
const profileImageInput = document.getElementById('id_profile_image');
|
||||
const imagePreviewContainer = document.getElementById('image-preview-container');
|
||||
@ -286,9 +152,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
reader.onload = function(e) {
|
||||
if (imagePreviewContainer) {
|
||||
imagePreviewContainer.innerHTML = `
|
||||
<img src="${e.target.result}" alt="Profile Preview" class="profile-image-preview">
|
||||
<h5 class="text-muted mt-3">${file.name}</h5>
|
||||
<p class="text-muted small">{% trans "New photo selected" %}</p>
|
||||
<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-gray-600 text-sm font-medium">${file.name}</h5>
|
||||
<p class="text-gray-500 text-xs">{% trans "New photo selected" %}</p>
|
||||
`;
|
||||
}
|
||||
};
|
||||
@ -305,15 +171,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (form) {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const submitBtn = form.querySelector('button[type="submit"]');
|
||||
submitBtn.classList.add('loading');
|
||||
|
||||
// Add loading state
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 mr-2 animate-spin"></i> {% trans "Saving..." %}`;
|
||||
|
||||
// Basic validation
|
||||
const name = document.getElementById('id_name');
|
||||
if (name && !name.value.trim()) {
|
||||
e.preventDefault();
|
||||
submitBtn.classList.remove('loading');
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Update" %}`;
|
||||
alert('{% trans "Name is required." %}');
|
||||
return;
|
||||
}
|
||||
@ -321,8 +189,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const email = document.getElementById('id_email');
|
||||
if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
|
||||
e.preventDefault();
|
||||
submitBtn.classList.remove('loading');
|
||||
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." %}');
|
||||
return;
|
||||
}
|
||||
@ -337,9 +205,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Warn before leaving if changes are made
|
||||
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() {
|
||||
formChanged = true;
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,358 +1,214 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% blocktrans %}Exam Stage- {{ job.title }} - 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;
|
||||
}
|
||||
{% block title %}{% blocktrans %}Exam Stage - {{ job.title }} - University ATS{% endblocktrans %}{% endblock %}
|
||||
|
||||
/* 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 %}
|
||||
<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>
|
||||
<h1 class="h3 mb-1 page-header">
|
||||
<i class="fas fa-edit me-2"></i>
|
||||
{% trans "Exam Management" %} - {{ job.title }}
|
||||
<h1 class="text-2xl sm:text-3xl font-bold text-temple-dark mb-2 flex items-center gap-2">
|
||||
<i data-lucide="edit-3" class="w-6 h-6 sm:w-7 sm:h-7"></i>
|
||||
{% trans "Exam Management" %}
|
||||
</h1>
|
||||
<h2 class="h5 text-muted mb-0">
|
||||
{% trans "Applications in Exam Stage:" %} <span class="fw-bold">{{ total_candidates }}</span>
|
||||
</h2>
|
||||
<p class="text-gray-500 text-sm sm:text-base">
|
||||
{% trans "Job:" %} {{ job.title }}
|
||||
<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 class="d-flex gap-2">
|
||||
<div class="flex gap-2 w-full sm:w-auto">
|
||||
<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' %}">
|
||||
<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 href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Job" %}
|
||||
<a href="{% url 'job_detail' job.slug %}"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="applicant-tracking-timeline mb-4">
|
||||
<!-- Applicant Tracking Timeline -->
|
||||
<div class="mb-6">
|
||||
{% include 'jobs/partials/applicant_tracking.html' %}
|
||||
</div>
|
||||
|
||||
<h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);">
|
||||
{% trans "Application List" %}
|
||||
<span class="badge bg-primary-theme ms-2">{{ applications|length }} / {{ total_candidates }} Total</span>
|
||||
<small class="text-muted fw-normal ms-2">({% trans "Sorted by AI Score" %})</small>
|
||||
</h2>
|
||||
<!-- 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" %}
|
||||
<span class="ml-2 px-2 py-1 bg-temple-red text-white text-xs rounded-full">
|
||||
{{ applications|length }} / {{ total_candidates }} {% trans "Total" %}
|
||||
</span>
|
||||
</h2>
|
||||
<div class="text-xs text-gray-500">
|
||||
{% trans "Sorted by AI Score" %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kaauh-card shadow-sm p-3">
|
||||
{% if applications %}
|
||||
<div class="bulk-action-bar p-3 bg-light border-bottom">
|
||||
<form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group">
|
||||
{% csrf_token %}
|
||||
<!-- Main Card -->
|
||||
<div class="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
|
||||
<!-- Bulk Action Bar -->
|
||||
{% if applications %}
|
||||
<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="flex flex-col sm:flex-row gap-3 sm:gap-4 items-end">
|
||||
{% csrf_token %}
|
||||
|
||||
{# Using d-flex for horizontal alignment and align-items-end to align items to the bottom baseline #}
|
||||
<div class="d-flex align-items-end gap-3">
|
||||
|
||||
{# 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="Interview">
|
||||
{% trans "Interview Stage" %}
|
||||
</option>
|
||||
<option value="Applied">
|
||||
{% trans "Screening Stage" %}
|
||||
</option>
|
||||
<div class="flex-1 w-full sm:w-auto">
|
||||
<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">
|
||||
<option selected>----------</option>
|
||||
<option value="Interview">{% trans "Interview Stage" %}</option>
|
||||
<option value="Applied">{% trans "Screening Stage" %}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{# Button #}
|
||||
<button id="changeStage" type="submit" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %}
|
||||
<button id="changeStage" type="submit"
|
||||
class="w-full sm:w-auto px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2">
|
||||
<i data-lucide="arrow-right" class="w-4 h-4"></i>
|
||||
{% trans "Change Stage" %}
|
||||
</button>
|
||||
|
||||
<button id="emailBotton" 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>
|
||||
<button id="emailBotton" type="button"
|
||||
class="w-full sm:w-auto px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium flex items-center justify-center gap-2"
|
||||
onclick="openEmailModal()"
|
||||
title="{% trans 'Email Participants' %}">
|
||||
<i data-lucide="mail" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% 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 %}
|
||||
<table class="table application-table align-middle">
|
||||
<thead>
|
||||
<table class="w-full border-collapse">
|
||||
<thead class="bg-gray-50">
|
||||
<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 %}
|
||||
<div class="form-check">
|
||||
<input
|
||||
type="checkbox" class="form-check-input" id="selectAllCheckbox">
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" class="h-4 w-4 text-temple-red rounded border-gray-300 focus:ring-temple-red" id="selectAllCheckbox">
|
||||
</div>
|
||||
{% endif %}
|
||||
</th>
|
||||
<th style="width: 15%;">{% trans "Name" %}</th>
|
||||
<th style="width: 15%;">{% trans "Contact Info" %}</th>
|
||||
<th style="width: 10%;" class="text-center">{% trans "AI Score" %}</th>
|
||||
<th style="width: 10%;">{% trans "Exam Date" %}</th>
|
||||
<th style="width: 10%;">{% trans "Exam Score" %}</th>
|
||||
<th style="width: 10%;" class="text-center">{% trans "Exam Results" %}</th>
|
||||
<th style="width: 10%"> {% trans "Notes"%}</th>
|
||||
<th style="width: 15%;">{% trans "Actions" %}</th>
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
||||
<i data-lucide="user" class="w-3 h-3 inline mr-1"></i> {% 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%]">
|
||||
<i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {% trans "Contact Info" %}
|
||||
</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="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>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for application in applications %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="form-check">
|
||||
<input
|
||||
name="candidate_ids"
|
||||
value="{{ application.id }}"
|
||||
type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-4 py-3 border-b border-gray-200">
|
||||
<div class="flex items-center">
|
||||
<input name="candidate_ids" value="{{ application.id }}"
|
||||
type="checkbox"
|
||||
class="h-4 w-4 text-temple-red rounded border-gray-300 focus:ring-temple-red rowCheckbox"
|
||||
id="application-{{ application.id }}">
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="application-name">
|
||||
{{ application.name }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="application-details">
|
||||
<i class="fas fa-envelope me-1"></i> {{ application.email }}<br>
|
||||
<i class="fas fa-phone me-1"></i> {{ application.phone }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge ai-score-badge">{{ application.match_score|default:"0" }}%</span>
|
||||
</td>
|
||||
<td>
|
||||
{{application.exam_date|date:"d-m-Y h:i A"|default:"--"}}
|
||||
</td>
|
||||
<td id="exam-score-{{ application.pk}}">
|
||||
{{application.exam_score|default:"--"}}
|
||||
</td>
|
||||
|
||||
<td class="text-center" id="status-result-{{ application.pk}}">
|
||||
{% if not application.exam_status %}
|
||||
<button type="button" class="btn btn-warning btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
title="Pass Exam">
|
||||
<i class="fas fa-plus"></i>
|
||||
</td>
|
||||
<td class="px-4 py-3 border-b border-gray-200">
|
||||
<div class="font-semibold text-temple-red">
|
||||
{{ application.name }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 border-b border-gray-200">
|
||||
<div class="text-xs text-gray-500">
|
||||
<i data-lucide="mail" class="w-3 h-3 inline mr-1"></i> {{ application.email }}<br>
|
||||
<i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {{ application.phone }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center border-b border-gray-200">
|
||||
<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 class="px-4 py-3 border-b border-gray-200 text-sm">
|
||||
{{application.exam_date|date:"d-m-Y h:i A"|default:"--"}}
|
||||
</td>
|
||||
<td class="px-4 py-3 border-b border-gray-200 text-sm" id="exam-score-{{ application.pk }}">
|
||||
{{application.exam_score|default:"--"}}
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center border-b border-gray-200" id="status-result-{{ application.pk }}">
|
||||
{% if not application.exam_status %}
|
||||
<button type="button"
|
||||
class="px-3 py-1.5 bg-yellow-400 hover:bg-yellow-500 text-yellow-900 rounded-lg transition text-xs font-medium flex items-center gap-1 mx-auto"
|
||||
onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}')"
|
||||
title="{% trans 'Pass Exam' %}">
|
||||
<i data-lucide="plus" class="w-3 h-3"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
{% if application.exam_status %}
|
||||
<button type="button"
|
||||
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"
|
||||
onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}')"
|
||||
title="{% trans 'Update Exam Status' %}">
|
||||
{{ application.exam_status }}
|
||||
</button>
|
||||
{% else %}
|
||||
{% if application.exam_status %}
|
||||
<button type="button" class="btn btn-{% if application.exam_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
title="Pass Exam">
|
||||
{{ application.exam_status }}
|
||||
</button>
|
||||
{% else %}
|
||||
--
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td><button type="button" class="btn btn-outline-primary btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#noteModal"
|
||||
hx-get="{% url 'application_add_note' application.slug %}"
|
||||
hx-swap="innerHTML"
|
||||
hx-target=".notemodal">
|
||||
<i class="fas fa-calendar-plus me-1"></i>
|
||||
Add note
|
||||
</button></td>
|
||||
|
||||
<td >
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
title="View Profile">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-4 py-3 border-b border-gray-200">
|
||||
<button type="button"
|
||||
class="px-3 py-1.5 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-xs font-medium flex items-center gap-1 mx-auto"
|
||||
onclick="openNoteModal('{% url 'application_add_note' application.slug %}')">
|
||||
<i data-lucide="plus-circle" class="w-3 h-3"></i>
|
||||
{% trans "Add note" %}
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-4 py-3 border-b border-gray-200 text-center">
|
||||
<button type="button"
|
||||
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"
|
||||
onclick="openCandidateModal('{% url 'application_criteria_view_htmx' application.pk %}')"
|
||||
title="{% trans 'View Application Profile' %}">
|
||||
<i data-lucide="eye" class="w-3 h-3"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if not applications %}
|
||||
<div class="alert alert-info text-center mt-3 mb-0" role="alert">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{% trans "No applications are currently in the Exam stage for this job." %}
|
||||
<div class="p-8 text-center bg-blue-50 border border-blue-200 rounded-lg m-4">
|
||||
<i data-lucide="info" class="w-8 h-8 text-blue-500 mx-auto mb-2"></i>
|
||||
<p class="text-blue-700 text-sm">{% trans "No applications are currently in the Exam stage for this job." %}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
@ -360,23 +216,30 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade modal-lg" id="candidateviewModal" tabindex="-1" aria-labelledby="candidateviewModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content kaauh-card">
|
||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
||||
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
|
||||
<!-- 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>
|
||||
|
||||
<!-- 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" %}
|
||||
</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 id="candidateviewModalBody" class="modal-body">
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
|
||||
{% trans "Loading application data..." %}
|
||||
<div id="candidateviewModalBody" 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 application data..." %}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="border-top: 1px solid var(--kaauh-border);">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
<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>
|
||||
@ -384,47 +247,260 @@
|
||||
</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 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>
|
||||
|
||||
<!-- 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>
|
||||
</div>
|
||||
</div>
|
||||
{% include "recruitment/partials/note_modal.html" %}
|
||||
{% include "recruitment/partials/stage_confirmation_modal.html" %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
// Reinitialize Lucide icons after content loads
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
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 () {
|
||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||
const rowCheckboxes = document.querySelectorAll('.rowCheckbox');
|
||||
const changeStageButton = document.getElementById('changeStage');
|
||||
const emailButton = document.getElementById('emailBotton');
|
||||
const updateStatus = document.getElementById('update_status');
|
||||
const stageConfirmationModal = new bootstrap.Modal(document.getElementById('stageConfirmationModal'));
|
||||
const confirmStageChangeButton = document.getElementById('confirmStageChangeButton');
|
||||
let isConfirmed = false;
|
||||
|
||||
if (selectAllCheckbox) {
|
||||
|
||||
// Function to safely update the header checkbox state
|
||||
function updateSelectAllState() {
|
||||
const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
|
||||
@ -433,26 +509,25 @@
|
||||
if (checkedCount === 0) {
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
changeStageButton.disabled = true;
|
||||
emailButton.disabled = true;
|
||||
updateStatus.disabled = true;
|
||||
if (changeStageButton) changeStageButton.disabled = true;
|
||||
if (emailButton) emailButton.disabled = true;
|
||||
if (updateStatus) updateStatus.disabled = true;
|
||||
} else if (checkedCount === totalCount) {
|
||||
selectAllCheckbox.checked = true;
|
||||
selectAllCheckbox.indeterminate = false;
|
||||
changeStageButton.disabled = false;
|
||||
emailButton.disabled = false;
|
||||
updateStatus.disabled = false;
|
||||
if (changeStageButton) changeStageButton.disabled = false;
|
||||
if (emailButton) emailButton.disabled = false;
|
||||
if (updateStatus) updateStatus.disabled = false;
|
||||
} else {
|
||||
// Set to indeterminate state (partially checked)
|
||||
selectAllCheckbox.checked = false;
|
||||
selectAllCheckbox.indeterminate = true;
|
||||
changeStageButton.disabled = false;
|
||||
emailButton.disabled = false;
|
||||
updateStatus.disabled = false;
|
||||
if (changeStageButton) changeStageButton.disabled = false;
|
||||
if (emailButton) emailButton.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 () {
|
||||
const isChecked = selectAllCheckbox.checked;
|
||||
|
||||
@ -467,12 +542,10 @@
|
||||
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();
|
||||
}
|
||||
|
||||
@ -481,51 +554,34 @@
|
||||
changeStageButton.addEventListener('click', function(event) {
|
||||
const selectedStage = updateStatus.value;
|
||||
|
||||
// Check if a stage is selected (not default empty option)
|
||||
if (selectedStage && selectedStage.trim() !== '') {
|
||||
// If not yet confirmed, show modal and prevent submission
|
||||
if (!isConfirmed) {
|
||||
event.preventDefault();
|
||||
|
||||
// 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();
|
||||
openStageConfirmationModal(selectedStage);
|
||||
return false;
|
||||
}
|
||||
// If confirmed, let's form submit normally (reset flag for next time)
|
||||
isConfirmed = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle confirm button click in modal
|
||||
if (confirmStageChangeButton) {
|
||||
confirmStageChangeButton.addEventListener('click', function() {
|
||||
// Hide modal
|
||||
stageConfirmationModal.hide();
|
||||
|
||||
// Set confirmed flag
|
||||
closeStageConfirmationModal();
|
||||
isConfirmed = true;
|
||||
|
||||
// Programmatically trigger's button click to submit form
|
||||
changeStageButton.click();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Close modals on escape key
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeCandidateModal();
|
||||
closeEmailModal();
|
||||
closeNoteModal();
|
||||
closeStageConfirmationModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% 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
File diff suppressed because it is too large
Load Diff
@ -3,236 +3,72 @@
|
||||
|
||||
{% 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 %}
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Header Section -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
<h1 class="text-3xl font-bold text-temple-red mb-2 flex items-center gap-3">
|
||||
<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" %}
|
||||
</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." %}
|
||||
</p>
|
||||
</div>
|
||||
<a href="{% url 'candidate_detail' object.slug %}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Applicant" %}
|
||||
<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 data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Applicant" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="flex justify-center">
|
||||
<div class="w-full max-w-4xl">
|
||||
<!-- Warning Section -->
|
||||
<div class="warning-section">
|
||||
<div class="warning-icon">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<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="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>
|
||||
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||
<p class="warning-text">
|
||||
{% trans "Deleting this applicant will permanently remove all associated data. Please review the information below carefully before proceeding." %}
|
||||
<h3 class="text-2xl font-bold text-amber-800 mb-3">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||
<p class="text-amber-700">
|
||||
{% trans "Deleting this applicant will permanently remove all associated data. Please review information below carefully before proceeding." %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Candidate Information -->
|
||||
<div class="card kaauh-card mb-4">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-user me-2"></i>
|
||||
<!-- Applicant Info Card -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||
<div class="bg-white border-b border-gray-200 p-5">
|
||||
<h5 class="text-lg font-bold text-temple-red flex items-center gap-2">
|
||||
<i data-lucide="user" class="w-5 h-5"></i>
|
||||
{% trans "Applicant to be Deleted" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="candidate-info">
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<div class="p-6">
|
||||
<div class="bg-gray-50 rounded-xl p-6 mb-6">
|
||||
<div class="flex items-center gap-4 mb-5 pb-5 border-b border-gray-200">
|
||||
{% 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 %}
|
||||
<div class="avatar-placeholder me-3">
|
||||
<i class="fas fa-user text-muted fa-2x"></i>
|
||||
<div class="w-20 h-20 rounded-full bg-gray-200 flex items-center justify-center border-3 border-temple-red">
|
||||
<i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<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 %}
|
||||
<p class="text-muted mb-0">{{ object.email }}</p>
|
||||
<p class="text-gray-600">{{ object.email }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-briefcase"></i>
|
||||
<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="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||
<i data-lucide="briefcase" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Position Applied" %}</div>
|
||||
<div class="info-value">
|
||||
<div class="flex-1">
|
||||
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Position Applied" %}</div>
|
||||
<div class="text-gray-900 text-base">
|
||||
{% if object.job_posting %}
|
||||
{{ object.job_posting.title }}
|
||||
{% else %}
|
||||
@ -243,36 +79,38 @@
|
||||
</div>
|
||||
|
||||
{% if object.phone %}
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-phone"></i>
|
||||
<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="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||
<i data-lucide="phone" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Phone" %}</div>
|
||||
<div class="info-value">{{ object.phone }}</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Phone" %}</div>
|
||||
<div class="text-gray-900 text-base">{{ object.phone }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-calendar"></i>
|
||||
<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="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||
<i data-lucide="calendar" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Applied On" %}</div>
|
||||
<div class="info-value">{{ object.created_at|date:"F d, Y" }}</div>
|
||||
<div class="flex-1">
|
||||
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Applied On" %}</div>
|
||||
<div class="text-gray-900 text-base">{{ object.created_at|date:"F d, Y" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<div class="flex items-start gap-4">
|
||||
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||
<i data-lucide="info" class="w-5 h-5"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Status" %}</div>
|
||||
<div class="info-value">
|
||||
<div class="flex-1">
|
||||
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Status" %}</div>
|
||||
<div class="text-gray-900 text-base">
|
||||
{% 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 %}
|
||||
{% trans "Not specified" %}
|
||||
{% endif %}
|
||||
@ -283,34 +121,34 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Consequences -->
|
||||
<div class="card kaauh-card mb-4">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-list me-2"></i>
|
||||
<!-- Consequences Card -->
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||
<div class="bg-white border-b border-gray-200 p-5">
|
||||
<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 applicant?" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="consequence-list">
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<div class="p-6">
|
||||
<ul class="space-y-3">
|
||||
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<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" %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<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" %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<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" %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||
<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" %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
<li class="flex items-start gap-2 text-gray-700">
|
||||
<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" %}
|
||||
</li>
|
||||
</ul>
|
||||
@ -318,30 +156,27 @@
|
||||
</div>
|
||||
|
||||
<!-- Confirmation Form -->
|
||||
<div class="card kaauh-card">
|
||||
<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">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required>
|
||||
<label class="form-check-label" for="confirm_delete">
|
||||
<div class="mb-6">
|
||||
<label class="flex items-start gap-3 cursor-pointer">
|
||||
<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">
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'candidate_detail' object.slug %}" class="btn btn-secondary btn-lg">
|
||||
<i class="fas fa-times me-2"></i>
|
||||
<div class="flex flex-col sm:flex-row gap-3 justify-between">
|
||||
<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 data-lucide="x" class="w-5 h-5"></i>
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="btn btn-danger btn-lg"
|
||||
id="deleteButton"
|
||||
disabled>
|
||||
<i class="fas fa-trash me-2"></i>
|
||||
<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">
|
||||
<i data-lucide="trash-2" class="w-5 h-5"></i>
|
||||
{% trans "Delete Applicant Permanently" %}
|
||||
</button>
|
||||
</div>
|
||||
@ -361,26 +196,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function validateForm() {
|
||||
const checkboxChecked = confirmDeleteCheckbox.checked;
|
||||
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);
|
||||
validateForm();
|
||||
|
||||
// Add confirmation before final submission
|
||||
/*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();
|
||||
}
|
||||
});
|
||||
*/
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,33 +1,64 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %} {% trans "Interview Calendar" %} {% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--calendar-color: #00636e;
|
||||
--calendar-light: rgba(0, 99, 110, 0.1);
|
||||
--calendar-hover: rgba(0, 99, 110, 0.2);
|
||||
--calendar-color: #9d2235;
|
||||
--calendar-light: rgba(157, 34, 53, 0.1);
|
||||
}
|
||||
|
||||
.calendar-container {
|
||||
background-color: white;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.fc-toolbar-title {
|
||||
color: var(--calendar-color) !important;
|
||||
color: #9d2235 !important;
|
||||
font-weight: 700 !important;
|
||||
font-size: 1.5rem !important;
|
||||
}
|
||||
|
||||
.fc-button-primary {
|
||||
background-color: var(--calendar-color) !important;
|
||||
border-color: var(--calendar-color) !important;
|
||||
background-color: #9d2235 !important;
|
||||
border-color: #9d2235 !important;
|
||||
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 {
|
||||
@ -41,6 +72,7 @@
|
||||
padding: 0.25rem 0.6rem;
|
||||
border-radius: 1rem;
|
||||
font-weight: 600;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.status-scheduled { background-color: #e3f2fd; color: #0d47a1; }
|
||||
@ -53,71 +85,84 @@
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
background-color: #f9fbfd;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px dashed #dee2e6;
|
||||
padding: 1.25rem;
|
||||
background-color: #f9fafb;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.85rem;
|
||||
color: #495057;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
color: #4b5563;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||
<div>
|
||||
<h1 class="h3 mb-1 page-header">{% trans "Interview Calendar" %}</h1>
|
||||
<p class="text-muted mb-0">{{ job.title|default:"Global Schedule" }}</p>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="text-muted small mb-0">Tenhal | تنحل</p>
|
||||
<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="calendar" class="w-8 h-8 text-temple-red"></i>
|
||||
</div>
|
||||
{% trans "Interview Calendar" %}
|
||||
</h1>
|
||||
<p class="text-gray-600">{{ job.title|default:"Global Schedule" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calendar-container">
|
||||
<!-- Calendar -->
|
||||
<div class="calendar-container mb-6">
|
||||
<div id="calendar"></div>
|
||||
|
||||
<div class="calendar-legend">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="interview-details mt-4" id="interview-details" style="display: none;">
|
||||
<div class="card shadow-sm border-start border-4 border-info">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3">
|
||||
<h5 class="mb-0 text-primary-theme"><i class="fas fa-info-circle me-2"></i>{% trans "Interview Details" %}</h5>
|
||||
<button type="button" class="btn-close" id="close-details"></button>
|
||||
<!-- Interview Details -->
|
||||
<div class="interview-details hidden" id="interview-details">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="p-6 border-b border-gray-200 flex justify-between items-center bg-white">
|
||||
<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 class="p-6" id="interview-info">
|
||||
</div>
|
||||
<div class="card-body" id="interview-info">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -129,7 +174,6 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
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);
|
||||
|
||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
@ -160,45 +204,49 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const statusText = status.charAt(0).toUpperCase() + status.slice(1);
|
||||
|
||||
let meetingInfo = '';
|
||||
// FIX: Corrected translation tags and property checks
|
||||
if (event.extendedProps.meeting_id) {
|
||||
meetingInfo = `
|
||||
<div class="mt-3 p-3 bg-light rounded border">
|
||||
<h6 class="text-primary-theme"><i class="fas fa-video me-2"></i>{% trans "Meeting Information" %}</h6>
|
||||
<p class="mb-1"><strong>{% trans "Meeting ID:" %}</strong> ${event.extendedProps.meeting_id}</p>
|
||||
<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>
|
||||
<div class="mt-3 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<h6 class="text-temple-red font-bold mb-2 flex items-center gap-2">
|
||||
<i data-lucide="video" class="w-4 h-4"></i>
|
||||
{% 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>
|
||||
`;
|
||||
}
|
||||
|
||||
infoContainer.innerHTML = `
|
||||
<div class="row">
|
||||
<div class="col-md-6 border-end">
|
||||
<h6 class="text-muted text-uppercase small fw-bold">{% trans "Candidate" %}</h6>
|
||||
<p class="mb-1"><strong>{% trans "Name:" %}</strong> ${event.extendedProps.candidate}</p>
|
||||
<p><strong>{% trans "Email:" %}</strong> ${event.extendedProps.email}</p>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="border-r border-gray-200 pr-0 md:pr-6">
|
||||
<h6 class="text-gray-500 text-xs font-bold uppercase mb-3">{% trans "Candidate" %}</h6>
|
||||
<p class="mb-2 text-sm"><strong>{% trans "Name:" %}</strong> ${event.extendedProps.candidate}</p>
|
||||
<p class="text-sm"><strong>{% trans "Email:" %}</strong> ${event.extendedProps.email}</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6 class="text-muted text-uppercase small fw-bold">{% trans "Schedule" %}</h6>
|
||||
<p class="mb-1"><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><strong>{% trans "Status:" %}</strong> <span class="status-badge ${statusClass}">${statusText}</span></p>
|
||||
<div>
|
||||
<h6 class="text-gray-500 text-xs font-bold uppercase mb-3">{% trans "Schedule" %}</h6>
|
||||
<p class="mb-2 text-sm"><strong>{% trans "Date:" %}</strong> ${event.start.toLocaleDateString()}</p>
|
||||
<p class="mb-2 text-sm"><strong>{% trans "Time:" %}</strong> ${event.start.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</p>
|
||||
<p class="text-sm"><strong>{% trans "Status:" %}</strong> <span class="status-badge ${statusClass}">${statusText}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
${meetingInfo}
|
||||
<div class="mt-4 pt-3 border-top">
|
||||
<a href="${event.url}" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-external-link-alt me-1"></i> {% trans "View Full Application" %}
|
||||
<div class="mt-4 pt-3 border-t border-gray-200">
|
||||
<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 data-lucide="external-link" class="w-4 h-4"></i>
|
||||
{% trans "View Full Application" %}
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
detailsContainer.style.display = 'block';
|
||||
detailsContainer.classList.remove('hidden');
|
||||
detailsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
document.getElementById('close-details').addEventListener('click', function() {
|
||||
document.getElementById('interview-details').style.display = 'none';
|
||||
document.getElementById('interview-details').classList.add('hidden');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -1,144 +1,130 @@
|
||||
<!-- templates/recruitment/interview_detail.html -->
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block customCSS %}
|
||||
<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 title %}{% trans "Interview Details" %} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="detail-header">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h1 class="h3 mb-0">{% trans "Interview Details" %}</h1>
|
||||
<div>
|
||||
<span class="h5">{{ job.title }}</span>
|
||||
<div class="px-4 py-6">
|
||||
<!-- Header -->
|
||||
<div class="bg-gradient-to-br from-temple-red to-red-800 rounded-xl shadow-xl p-6 mb-6 text-white">
|
||||
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||
<div class="flex-1">
|
||||
<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 class="card detail-card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>{% trans "Applicant Information"%}</h5>
|
||||
<table class="table table-borderless">
|
||||
<tr>
|
||||
<td><strong>{% trans "Name:" %}</strong></td>
|
||||
<td>{{ interview.candidate.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{% trans "Email:" %}</strong></td>
|
||||
<td>{{ interview.candidate.email }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{% trans "Phone:" %}</strong></td>
|
||||
<td>{{ interview.candidate.phone|default:"Not provided" }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- Details Card -->
|
||||
<div class="bg-white rounded-xl shadow-md border-l-4 border-temple-red overflow-hidden mb-6">
|
||||
<div class="p-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- Applicant Information -->
|
||||
<div class="bg-gray-50 rounded-lg p-5">
|
||||
<h3 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||
<i data-lucide="user" class="w-5 h-5"></i>
|
||||
{% trans "Applicant Information" %}
|
||||
</h3>
|
||||
<div class="space-y-3">
|
||||
<div class="flex">
|
||||
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Name:" %}</span>
|
||||
<span class="text-gray-900">{{ interview.candidate.name }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Email:" %}</span>
|
||||
<span class="text-gray-900">{{ interview.candidate.email }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Phone:" %}</span>
|
||||
<span class="text-gray-900">{{ interview.candidate.phone|default:"{% trans 'Not provided' %}" }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5>{% trans "Interview Details" %}</h5>
|
||||
<table class="table table-borderless">
|
||||
<tr>
|
||||
<td><strong>{% trans "Date:" %}</strong></td>
|
||||
<td>{{ interview.interview_date|date:"l, F j, Y" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{% trans "Time:" %}</strong></td>
|
||||
<td>{{ interview.interview_time|time:"g:i A" }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{% trans "Status:" %}</strong></td>
|
||||
<td>
|
||||
<span class="status-badge status-{{ interview.status }}">
|
||||
{{ interview.status|title }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Interview Details -->
|
||||
<div class="bg-gray-50 rounded-lg p-5">
|
||||
<h3 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||
<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 }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Meeting Information (if Zoom meeting exists) -->
|
||||
{% if interview.zoom_meeting %}
|
||||
<div class="mt-4">
|
||||
<h5>{% trans "Meeting Information" %}</h5>
|
||||
<table class="table table-borderless">
|
||||
<tr>
|
||||
<td><strong>{% trans "Meeting ID:" %}</strong></td>
|
||||
<td>{{ interview.zoom_meeting.meeting_id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{% trans "Topic:" %}</strong></td>
|
||||
<td>{{ interview.zoom_meeting.topic }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{% trans "Duration:" %}</strong></td>
|
||||
<td>{{ interview.zoom_meeting.duration }} {% trans "minutes" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>{% trans "Join URL:" %}</strong></td>
|
||||
<td><a href="{{ interview.zoom_meeting.join_url }}" target="_blank">{{ interview.zoom_meeting.join_url }}</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="mt-6 bg-gray-50 rounded-lg p-5">
|
||||
<h3 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||
<i data-lucide="video" class="w-5 h-5"></i>
|
||||
{% trans "Meeting Information" %}
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="flex">
|
||||
<span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Meeting ID:" %}</span>
|
||||
<span class="text-gray-900 font-mono">{{ interview.zoom_meeting.meeting_id }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Topic:" %}</span>
|
||||
<span class="text-gray-900">{{ interview.zoom_meeting.topic }}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Duration:" %}</span>
|
||||
<span class="text-gray-900">{{ interview.zoom_meeting.duration }} {% trans "minutes" %}</span>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'interview_calendar' slug=job.slug %}" class="btn btn-secondary">
|
||||
<i class="fas fa-calendar"></i> {% trans "Back to Calendar" %}
|
||||
<!-- Action Buttons -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<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>
|
||||
{% if interview.status == 'scheduled' %}
|
||||
<button class="btn btn-success">
|
||||
<i class="fas fa-check"></i> {% trans "Confirm Interview" %}
|
||||
<button type="button"
|
||||
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>
|
||||
{% endif %}
|
||||
{% if interview.status != 'cancelled' and interview.status != 'completed' %}
|
||||
<button class="btn btn-danger">
|
||||
<i class="fas fa-times"></i> {% trans "Cancel Interview" %}
|
||||
<button type="button"
|
||||
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>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -147,3 +133,12 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,66 +1,75 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Mark All as Read" %} - ATS{% endblock %}
|
||||
{% block title %}{% trans "Mark All as Read" %} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body text-center p-5">
|
||||
<div class="mb-4">
|
||||
<i class="fas fa-check-double fa-3x text-success"></i>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex justify-center">
|
||||
<div class="w-full max-w-2xl">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8 text-center">
|
||||
<div class="mb-6">
|
||||
<div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto">
|
||||
<i data-lucide="check-circle-2" class="w-12 h-12 text-green-600"></i>
|
||||
</div>
|
||||
|
||||
<h4 class="mb-3">{{ title }}</h4>
|
||||
<p class="text-muted mb-4">{{ message }}</p>
|
||||
|
||||
{% if unread_count > 0 %}
|
||||
<div class="alert alert-info mb-4">
|
||||
<h6 class="alert-heading">
|
||||
<i class="fas fa-info-circle me-2"></i>{% trans "What this will do" %}
|
||||
</h6>
|
||||
<p class="mb-2">
|
||||
{% blocktrans count count=unread_count %}
|
||||
This will mark {{ count }} unread notification as read.
|
||||
{% plural %}
|
||||
This will mark all {{ count }} unread notifications as read.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p class="mb-0">
|
||||
{% trans "You can still view all notifications in your notification list, but they won't appear as unread." %}
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-success mb-4">
|
||||
<h6 class="alert-heading">
|
||||
<i class="fas fa-check-circle me-2"></i>{% trans "All caught up!" %}
|
||||
</h6>
|
||||
<p class="mb-0">
|
||||
{% trans "You don't have any unread notifications to mark as read." %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if unread_count > 0 %}
|
||||
<form method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-success">
|
||||
<i class="fas fa-check-double me-1"></i> {% trans "Yes, Mark All as Read" %}
|
||||
</button>
|
||||
<a href="{{ cancel_url }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||
</a>
|
||||
</form>
|
||||
{% else %}
|
||||
<a href="{{ cancel_url }}" class="btn btn-primary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Notifications" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h4 class="text-2xl font-bold text-gray-900 mb-3">{{ title }}</h4>
|
||||
<p class="text-gray-600 mb-6">{{ message }}</p>
|
||||
|
||||
{% if unread_count > 0 %}
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-xl p-5 mb-6 text-left">
|
||||
<h6 class="text-lg font-bold text-blue-800 flex items-center gap-2 mb-3">
|
||||
<i data-lucide="info" class="w-5 h-5"></i>
|
||||
{% trans "What this will do" %}
|
||||
</h6>
|
||||
<p class="text-blue-700 mb-3">
|
||||
{% blocktrans count count=unread_count %}
|
||||
This will mark {{ count }} unread notification as read.
|
||||
{% plural %}
|
||||
This will mark all {{ count }} unread notifications as read.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p class="text-blue-700">
|
||||
{% trans "You can still view all notifications in your notification list, but they won't appear as unread." %}
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="bg-green-50 border border-green-200 rounded-xl p-5 mb-6 text-left">
|
||||
<h6 class="text-lg font-bold text-green-800 flex items-center gap-2 mb-2">
|
||||
<i data-lucide="check-circle" class="w-5 h-5"></i>
|
||||
{% trans "All caught up!" %}
|
||||
</h6>
|
||||
<p class="text-green-700">
|
||||
{% trans "You don't have any unread notifications to mark as read." %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if unread_count > 0 %}
|
||||
<form method="post" class="inline-flex gap-3">
|
||||
{% csrf_token %}
|
||||
<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 data-lucide="check-circle-2" class="w-4 h-4"></i>
|
||||
{% trans "Yes, Mark All as Read" %}
|
||||
</button>
|
||||
<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 data-lucide="x" class="w-4 h-4"></i>
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</form>
|
||||
{% else %}
|
||||
<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 data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||
{% trans "Back to Notifications" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,41 +1,47 @@
|
||||
{% extends 'base.html' %}
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Delete Notification" %} - ATS{% endblock %}
|
||||
{% block title %}{% trans "Delete Notification" %} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-body text-center p-5">
|
||||
<div class="mb-4">
|
||||
<i class="fas fa-exclamation-triangle fa-3x text-warning"></i>
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex justify-center">
|
||||
<div class="w-full max-w-2xl">
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8 text-center">
|
||||
<div class="mb-6">
|
||||
<div class="w-20 h-20 bg-amber-100 rounded-full flex items-center justify-center mx-auto">
|
||||
<i data-lucide="alert-triangle" class="w-12 h-12 text-amber-500"></i>
|
||||
</div>
|
||||
|
||||
<h4 class="mb-3">{{ title }}</h4>
|
||||
<p class="text-muted mb-4">{{ message }}</p>
|
||||
|
||||
<div class="alert alert-light mb-4">
|
||||
<h6 class="alert-heading">{% trans "Notification Preview" %}</h6>
|
||||
<p class="mb-2"><strong>{% trans "Message:" %}</strong> {{ notification.message|truncatewords:20 }}</p>
|
||||
<p class="mb-0">
|
||||
<strong>{% trans "Created:" %}</strong> {{ notification.created_at|date:"Y-m-d H:i" }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger">
|
||||
<i class="fas fa-trash me-1"></i> {% trans "Yes, Delete" %}
|
||||
</button>
|
||||
<a href="{{ cancel_url }}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h4 class="text-2xl font-bold text-gray-900 mb-3">{{ title }}</h4>
|
||||
<p class="text-gray-600 mb-6">{{ message }}</p>
|
||||
|
||||
<div class="bg-gray-50 border border-gray-200 rounded-xl p-5 mb-6 text-left">
|
||||
<h6 class="text-lg font-bold text-gray-800 mb-3">{% trans "Notification Preview" %}</h6>
|
||||
<p class="mb-2"><strong class="text-gray-700">{% trans "Message:" %}</strong> {{ notification.message|truncatewords:20 }}</p>
|
||||
<p>
|
||||
<strong class="text-gray-700">{% trans "Created:" %}</strong> {{ notification.created_at|date:"Y-m-d H:i" }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form method="post" class="inline-flex gap-3">
|
||||
{% csrf_token %}
|
||||
<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 data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
{% trans "Yes, Delete" %}
|
||||
</button>
|
||||
<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 data-lucide="x" class="w-4 h-4"></i>
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
lucide.createIcons();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,63 +1,78 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Notifications" %} - ATS{% endblock %}
|
||||
{% block title %}{% trans "Notifications" %} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-bell me-2"></i>
|
||||
<div class="px-4 py-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-6">
|
||||
<div class="flex-1">
|
||||
<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" %}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
<p class="text-gray-600">
|
||||
{% blocktrans count count=total_notifications %}
|
||||
{{ count }} notification
|
||||
{% plural %}
|
||||
{{ count }} notifications
|
||||
{% endblocktrans %}
|
||||
{% if unread_notifications %}({{ unread_notifications }} unread){% endif %}
|
||||
{% if unread_notifications %}
|
||||
<span class="font-semibold text-temple-red">({{ unread_notifications }} unread)</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<div class="flex gap-2">
|
||||
{% if unread_notifications %}
|
||||
<a href="{% url 'notification_mark_all_read' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-check-double me-1"></i> {% trans "Mark All Read" %}
|
||||
<a href="{% url 'notification_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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label for="status_filter" class="form-label">{% trans "Status" %}</label>
|
||||
<select name="status" id="status_filter" class="form-select">
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200 mb-6">
|
||||
<div class="p-6">
|
||||
<form method="get" class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label for="status_filter" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
{% 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="unread" {% if status_filter == 'unread' %}selected{% endif %}>{% trans "Unread" %}</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>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="type_filter" class="form-label">{% trans "Type" %}</label>
|
||||
<select name="type" id="type_filter" class="form-select">
|
||||
<div>
|
||||
<label for="type_filter" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
{% 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="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>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label"> </label>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-filter me-1"></i> {% trans "Filter" %}
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2"> </label>
|
||||
<div class="flex gap-2">
|
||||
<button type="submit"
|
||||
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>
|
||||
<a href="{% url 'notification_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
||||
<a href="{% url 'notification_list' %}"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
@ -66,118 +81,111 @@
|
||||
</div>
|
||||
|
||||
<!-- Statistics -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
<div class="card border-primary">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title text-primary">{{ total_notifications }}</h5>
|
||||
<p class="card-text">{% trans "Total Notifications" %}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-white rounded-xl shadow-md border border-temple-red p-6 text-center">
|
||||
<h3 class="text-3xl font-bold text-temple-red mb-1">{{ total_notifications }}</h3>
|
||||
<p class="text-gray-600">{% trans "Total Notifications" %}</p>
|
||||
</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 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 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-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>
|
||||
|
||||
<!-- Notifications List -->
|
||||
{% if page_obj %}
|
||||
<div class="card">
|
||||
<div class="card-body p-0">
|
||||
<div class="list-group list-group-flush">
|
||||
{% for notification in page_obj %}
|
||||
<div class="list-group-item list-group-item-action {% if notification.status == 'PENDING' %}bg-light{% endif %}">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="badge bg-{{ notification.get_status_bootstrap_class }} me-2">
|
||||
{{ notification.get_status_display }}
|
||||
</span>
|
||||
<span class="badge bg-secondary me-2">
|
||||
{{ notification.get_notification_type_display }}
|
||||
</span>
|
||||
<small class="text-muted">{{ notification.created_at|date:"Y-m-d H:i" }}</small>
|
||||
</div>
|
||||
<h6 class="mb-1">
|
||||
<a href="{% url 'notification_detail' notification.id %}" class="text-decoration-none {% if notification.status == 'PENDING' %}fw-bold{% endif %}">
|
||||
{{ notification.message|truncatewords:15 }}
|
||||
</a>
|
||||
</h6>
|
||||
{% if notification.related_meeting %}
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-video me-1"></i>
|
||||
{% trans "Related to meeting:" %} {{ notification.related_meeting.topic }}
|
||||
</small>
|
||||
{% endif %}
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||
<div class="divide-y divide-gray-200">
|
||||
{% for notification in page_obj %}
|
||||
<div class="p-4 hover:bg-gray-50 transition {% if notification.status == 'PENDING' %}bg-blue-50{% endif %}">
|
||||
<div class="flex justify-between items-start gap-4">
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center flex-wrap gap-2 mb-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 }}
|
||||
</span>
|
||||
<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 }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-500">{{ notification.created_at|date:"Y-m-d H:i" }}</span>
|
||||
</div>
|
||||
<div class="d-flex flex-column gap-1">
|
||||
{% if notification.status == 'PENDING' %}
|
||||
<a href="{% url 'notification_mark_read' notification.id %}"
|
||||
class="btn btn-sm btn-outline-success"
|
||||
title="{% trans 'Mark as read' %}">
|
||||
<i class="fas fa-check"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'notification_mark_unread' notification.id %}"
|
||||
class="btn btn-sm btn-outline-secondary"
|
||||
title="{% trans 'Mark as unread' %}">
|
||||
<i class="fas fa-envelope"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'notification_delete' notification.id %}"
|
||||
class="btn btn-sm btn-outline-danger"
|
||||
title="{% trans 'Delete notification' %}">
|
||||
<i class="fas fa-trash"></i>
|
||||
<h5 class="text-base font-semibold mb-1">
|
||||
<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 }}
|
||||
</a>
|
||||
</div>
|
||||
</h5>
|
||||
{% if notification.related_meeting %}
|
||||
<p class="text-sm text-gray-500">
|
||||
<i data-lucide="video" class="w-3 h-3 inline mr-1"></i>
|
||||
{% trans "Related to meeting:" %} {{ notification.related_meeting.topic }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
{% if notification.status == 'PENDING' %}
|
||||
<a href="{% url 'notification_mark_read' notification.id %}"
|
||||
class="p-2 text-green-600 hover:bg-green-50 rounded-lg transition"
|
||||
title="{% trans 'Mark as read' %}">
|
||||
<i data-lucide="check" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'notification_mark_unread' notification.id %}"
|
||||
class="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition"
|
||||
title="{% trans 'Mark as unread' %}">
|
||||
<i data-lucide="mail" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'notification_delete' notification.id %}"
|
||||
class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition"
|
||||
title="{% trans 'Delete notification' %}">
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
<nav aria-label="{% trans 'Notifications pagination' %}" class="mt-4">
|
||||
<ul class="pagination justify-content-center">
|
||||
<nav aria-label="{% trans 'Notifications pagination' %}" class="mt-6">
|
||||
<ul class="flex justify-center items-center gap-2">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}&status={{ status_filter }}&type={{ type_filter }}">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
<li>
|
||||
<a class="px-3 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
|
||||
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>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% for num in page_obj.paginator.page_range %}
|
||||
{% if page_obj.number == num %}
|
||||
<li class="page-item active">
|
||||
<span class="page-link">{{ num }}</span>
|
||||
<li>
|
||||
<span class="px-4 py-2 bg-temple-red text-white rounded-lg font-semibold">{{ num }}</span>
|
||||
</li>
|
||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ num }}&status={{ status_filter }}&type={{ type_filter }}">{{ num }}</a>
|
||||
<li>
|
||||
<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>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}&status={{ status_filter }}&type={{ type_filter }}">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
<li>
|
||||
<a class="px-3 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
|
||||
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>
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -185,10 +193,10 @@
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="text-center py-5">
|
||||
<i class="fas fa-bell-slash fa-3x text-muted mb-3"></i>
|
||||
<h5 class="text-muted">{% trans "No notifications found" %}</h5>
|
||||
<p class="text-muted">
|
||||
<div class="text-center py-12 bg-white rounded-xl shadow-md border border-gray-200">
|
||||
<i data-lucide="bell-off" class="w-16 h-16 text-gray-400 mx-auto mb-4"></i>
|
||||
<h3 class="text-xl font-semibold text-gray-600 mb-2">{% trans "No notifications found" %}</h3>
|
||||
<p class="text-gray-500 mb-4">
|
||||
{% if status_filter or type_filter %}
|
||||
{% trans "Try adjusting your filters to see more notifications." %}
|
||||
{% else %}
|
||||
@ -196,8 +204,10 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if status_filter or type_filter %}
|
||||
<a href="{% url 'notification_list' %}" class="btn btn-main-action">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Clear Filters" %}
|
||||
<a href="{% url 'notification_list' %}"
|
||||
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>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -207,8 +217,11 @@
|
||||
|
||||
{% block customJS %}
|
||||
<script>
|
||||
/*
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Initialize Lucide icons
|
||||
lucide.createIcons();
|
||||
|
||||
/*
|
||||
// Auto-refresh notifications every 30 seconds
|
||||
setInterval(function() {
|
||||
fetch('/api/notification-count/')
|
||||
@ -219,15 +232,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
if (badge) {
|
||||
badge.textContent = data.count;
|
||||
if (data.count > 0) {
|
||||
badge.classList.remove('d-none');
|
||||
badge.classList.remove('hidden');
|
||||
} else {
|
||||
badge.classList.add('d-none');
|
||||
badge.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error fetching notifications:', error));
|
||||
}, 30000);
|
||||
*/
|
||||
});
|
||||
*/
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,51 +1,205 @@
|
||||
{% load i18n crispy_forms_tags %}
|
||||
<div class="p-3">
|
||||
<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 %}
|
||||
{{form|crispy}}
|
||||
<div class="modal-footer">
|
||||
<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>
|
||||
</div>
|
||||
</form>
|
||||
<div class="table-responsive mt-3">
|
||||
<table class="table table-sm" id="notesTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Author" %}</th>
|
||||
<th scope="col" style="width: 60%;">{% trans "Note" %}</th>
|
||||
<th scope="col">{% trans "Created" %}</th>
|
||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="note-table-body">
|
||||
{% if notes %}
|
||||
{% for note in notes %}
|
||||
<tr id="note-{{ note.id }}">
|
||||
<td class="align-middle">
|
||||
{{ note.author.get_full_name|default:note.author.username }}
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
{{ note.content|linebreaksbr }}
|
||||
</td>
|
||||
<td class="align-middle text-nowrap">
|
||||
<span class="text-muted">
|
||||
{{ note.created_at|date:"SHORT_DATETIME_FORMAT" }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="align-middle text-end">
|
||||
<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">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
|
||||
<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">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Crispy Form for rendering -->
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
{{ form|crispy }}
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
<tr class="border-b-2 border-temple-red">
|
||||
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark">
|
||||
<i data-lucide="user" class="w-3 h-3 inline mr-1"></i>
|
||||
{% trans "Author" %}
|
||||
</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>
|
||||
</thead>
|
||||
<tbody class="note-table-body">
|
||||
{% if notes %}
|
||||
{% for note in notes %}
|
||||
<tr id="note-{{ note.id }}" class="hover:bg-gray-50 transition-colors border-b border-gray-200">
|
||||
<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 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<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 }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap">
|
||||
<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" }}
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4 py-3 text-right">
|
||||
<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" %}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<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>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-3">{% trans "No notes yet." %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
@ -1,12 +1,17 @@
|
||||
{% load static i18n %}
|
||||
<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-content kaauh-card">
|
||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
<div class="modal-content bg-white border border-gray-200 rounded-xl shadow-lg">
|
||||
<div class="modal-header flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||
<h5 class="modal-title text-lg font-bold text-temple-dark" id="noteModalLabel">
|
||||
<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" data-bs-dismiss="modal" aria-label="Close">
|
||||
<i data-lucide="x" class="w-5 h-5"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body notemodal">
|
||||
|
||||
</div>
|
||||
<div class="modal-body notemodal p-6">
|
||||
<!-- Content will be loaded via HTMX -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,32 +1,34 @@
|
||||
{% load i18n %}
|
||||
<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-content kaauh-card">
|
||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
||||
<h5 class="modal-title" id="stageConfirmationModalLabel" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-info-circle me-2"></i>{% trans "Confirm Stage Change" %}
|
||||
<div class="modal-content bg-white border border-gray-200 rounded-xl shadow-lg">
|
||||
<div class="modal-header flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||
<h5 class="modal-title text-lg font-bold text-temple-dark" id="stageConfirmationModalLabel">
|
||||
<i data-lucide="info" class="w-5 h-5 inline mr-2"></i>{% trans "Confirm Stage Change" %}
|
||||
</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 class="modal-body">
|
||||
<div class="d-flex align-items-center justify-content-center py-3 mb-3">
|
||||
<i class="fas fa-exchange-alt fa-4x" style="color: var(--kaauh-teal);"></i>
|
||||
<div class="modal-body 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" 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>
|
||||
</p>
|
||||
<div class="alert alert-info text-center" role="alert">
|
||||
<i class="fas fa-user-check me-2"></i>
|
||||
<strong>{% trans "Selected Stage:" %}</strong>
|
||||
<span id="targetStageName" class="fw-bold">{% trans "--" %}</span>
|
||||
<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="modal-footer" style="border-top: 1px solid var(--kaauh-border);">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
<i class="fas fa-times me-1"></i>{% trans "Cancel" %}
|
||||
<div class="modal-footer 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" data-bs-dismiss="modal">
|
||||
<i data-lucide="x" class="w-4 h-4 inline mr-1"></i>{% trans "Cancel" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-main-action" id="confirmStageChangeButton">
|
||||
<i class="fas fa-check me-1"></i>{% trans "Confirm" %}
|
||||
<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>
|
||||
|
||||
@ -1,92 +1,106 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Schedule Meeting" %} - {{ job.title }} - ATS{% endblock %}
|
||||
{% block title %}{% trans "Schedule Meeting" %} - {{ job.title }} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1">
|
||||
<i class="fas fa-calendar-plus me-2"></i>
|
||||
<div class="px-4 py-6 max-w-2xl mx-auto">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-6">
|
||||
<div class="flex-1">
|
||||
<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 %}
|
||||
{% trans "Update Interview" %} for {{ application.name }}
|
||||
{% else %}
|
||||
{% trans "Schedule Interview" %} for {{ application.name }}
|
||||
{% endif %}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">{% trans "Job" %}: {{ job.title }}</p>
|
||||
{% if has_future_meeting %}
|
||||
<div class="alert alert-info mt-2 mb-0" role="alert">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{% trans "This application has upcoming interviews. You are updating an existing schedule." %}
|
||||
<p class="text-gray-600">{% trans "Job" %}: {{ job.title }}</p>
|
||||
{% if has_future_meeting %}
|
||||
<div class="mt-3 bg-blue-50 border border-blue-200 rounded-lg p-3 flex items-start gap-2">
|
||||
<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." %}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<a href="{% url 'applications_interview_view' job.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Candidates" %}
|
||||
<a href="{% url 'applications_interview_view' 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="arrow-left" class="w-4 h-4"></i>
|
||||
{% trans "Back to Candidates" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
<!-- Form Card -->
|
||||
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||
<div class="p-6">
|
||||
<form method="post" id="meetingForm">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.topic.id_for_label }}" class="form-label">
|
||||
<!-- Topic Field -->
|
||||
<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" %}
|
||||
</label>
|
||||
{{ form.topic }}
|
||||
{% if form.topic.errors %}
|
||||
<div class="text-danger">
|
||||
<div class="mt-1 text-sm text-red-600">
|
||||
{% for error in form.topic.errors %}
|
||||
<small>{{ error }}</small>
|
||||
<p>{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% 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." %}
|
||||
</div>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.start_time.id_for_label }}" class="form-label">
|
||||
<!-- Start Time Field -->
|
||||
<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" %}
|
||||
</label>
|
||||
{{ form.start_time }}
|
||||
{% 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 %}
|
||||
<small>{{ error }}</small>
|
||||
<p>{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-text">
|
||||
{% trans "Please select a date and time for the interview." %}
|
||||
</div>
|
||||
<p class="mt-1 text-sm text-gray-500">
|
||||
{% trans "Please select a date and time for interview." %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="{{ form.duration.id_for_label }}" class="form-label">
|
||||
<!-- Duration Field -->
|
||||
<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)" %}
|
||||
</label>
|
||||
{{ form.duration }}
|
||||
{% if form.duration.errors %}
|
||||
<div class="text-danger">
|
||||
<div class="mt-1 text-sm text-red-600">
|
||||
{% for error in form.duration.errors %}
|
||||
<small>{{ error }}</small>
|
||||
<p>{{ error }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save me-1"></i> {% trans "Schedule Meeting" %}
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex flex-col sm:flex-row gap-3 pt-4 border-t border-gray-200">
|
||||
<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>
|
||||
<a href="{% url 'applications_interview_view' job.slug %}" class="btn btn-secondary">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||
<a href="{% url 'applications_interview_view' job.slug %}"
|
||||
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>
|
||||
</div>
|
||||
</form>
|
||||
@ -94,3 +108,101 @@
|
||||
</div>
|
||||
</div>
|
||||
{% 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 %}
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,170 +1,273 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Sources" %}{% endblock %}
|
||||
{% block title %}{% trans "Sources" %} - University ATS{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="space-y-6">
|
||||
|
||||
<div class="container-fluid">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page" style="
|
||||
color: #F43B5E; /* Rosy Accent Color */
|
||||
font-weight: 600;
|
||||
">{% trans "Sources Settings" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- Search and Filters -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" class="row g-3">
|
||||
<div class="col-md-8">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text">
|
||||
<i class="fas fa-search"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control" name="q"
|
||||
placeholder="Search sources..." value="{{ search_query }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
<i class="fas fa-search"></i> {% trans "Search" %}
|
||||
</button>
|
||||
{% if search_query %}
|
||||
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-times"></i> {% trans "Clear" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
<!-- 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="database" class="w-6 h-6 text-temple-red"></i>
|
||||
</div>
|
||||
</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-4 py-2.5 rounded-xl text-sm transition shadow-sm">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add Source" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Results Summary -->
|
||||
{% if search_query %}
|
||||
<div class="alert alert-info">
|
||||
Found {{ total_sources }} source{{ total_sources|pluralize }} matching "{{ search_query }}"
|
||||
<!-- Mobile Filters -->
|
||||
<div class="space-y-3">
|
||||
<form method="get" class="flex gap-2">
|
||||
<div class="flex-1">
|
||||
<div class="relative">
|
||||
<i data-lucide="search" class="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2"></i>
|
||||
<input type="text" name="q"
|
||||
placeholder="{% trans 'Search sources...' %}"
|
||||
value="{{ search_query }}"
|
||||
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">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit" class="bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2.5 rounded-xl transition">
|
||||
<i data-lucide="search" class="w-4 h-4"></i>
|
||||
</button>
|
||||
{% if search_query %}
|
||||
<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 data-lucide="x" class="w-4 h-4"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sources Table -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th>{% trans "Status" %}</th>
|
||||
<th>{% trans "API Key" %}</th>
|
||||
<th>{% trans "Created" %}</th>
|
||||
<th>{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- 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>
|
||||
|
||||
{% for source in page_obj %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none text-primary-theme">
|
||||
<strong>{{ source.name }}</strong>
|
||||
</a>
|
||||
<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>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-primary-theme">{{ source.source_type }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if source.is_active %}
|
||||
<span class="badge bg-primary-theme">{% trans "Active" %}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-primary-theme">{% trans "Inactive" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<code class="small text-primary-theme">{{ source.api_key|truncatechars:20 }}</code>
|
||||
</td>
|
||||
<td>
|
||||
<small class="text-muted">{{ source.created_at|date:"M d, Y" }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'source_detail' source.pk %}"
|
||||
class="btn btn-sm btn-outline-primary" title="View">
|
||||
<i class="fas fa-eye"></i>
|
||||
</a>
|
||||
<a href="{% url 'source_update' source.pk %}"
|
||||
class="btn btn-sm btn-outline-secondary" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</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>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 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>
|
||||
|
||||
<!-- Pagination -->
|
||||
<!-- Results Summary -->
|
||||
{% if search_query %}
|
||||
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4 flex items-center gap-3">
|
||||
<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>
|
||||
{% endif %}
|
||||
|
||||
{% include "includes/paginator.html" %}
|
||||
{# --- MOBILE CARD VIEW --- #}
|
||||
<div class="lg:hidden grid grid-cols-1 gap-4">
|
||||
{% for source in page_obj %}
|
||||
<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 justify-between items-start">
|
||||
<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 %}
|
||||
<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">
|
||||
<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>
|
||||
<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 scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% 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 "Status" %}</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 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" class="px-6 py-3 text-center text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-white divide-y divide-gray-100">
|
||||
{% for source in page_obj %}
|
||||
<tr class="hover:bg-gray-50 transition-colors">
|
||||
<td class="px-6 py-4">
|
||||
<a href="{% url 'source_detail' source.pk %}" class="text-temple-red font-semibold hover:text-[#7a1a29] transition-colors">{{ source.name }}</a>
|
||||
<br>
|
||||
<span class="text-xs text-gray-500">ID: {{ source.pk }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<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 class="px-6 py-4">
|
||||
{% if source.is_active %}
|
||||
<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 %}
|
||||
<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 %}
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<code class="text-xs bg-gray-100 px-2 py-1 rounded text-temple-red">{{ source.api_key|truncatechars:20 }}</code>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">{{ source.created_at|date:"M d, Y" }}</td>
|
||||
<td class="px-6 py-4">
|
||||
<div class="flex items-center gap-2 justify-center">
|
||||
<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' %}">
|
||||
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||
</a>
|
||||
<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' %}">
|
||||
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</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 with query=query %}No sources match your search criteria "{{ query }}".{% endblocktrans %}
|
||||
{% 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="btn btn-main-action">
|
||||
<i class="fas fa-plus"></i> {% trans "Create Source" %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block customJS %}
|
||||
{% include "includes/paginator.html" %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Auto-refresh after status toggle
|
||||
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
||||
if (evt.detail.successful) {
|
||||
// Reload the page after a short delay to show updated status
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
lucide.createIcons();
|
||||
|
||||
// Auto-refresh after status toggle (for HTMX)
|
||||
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
||||
if (evt.detail.successful) {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user