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
|
# Job-specific Views
|
||||||
path("jobs/<slug:slug>/applicants/", views.job_applicants_view, name="job_applicants"),
|
path("jobs/<slug:slug>/applicants/", views.job_applicants_view, name="job_applicants"),
|
||||||
path("jobs/<slug:slug>/applications/", views.JobApplicationListView.as_view(), name="job_applications_list"),
|
path("jobs/<slug:slug>/applications/", views.JobApplicationListView.as_view(), name="job_applications_list"),
|
||||||
path("jobs/<slug:slug>/calendar/", views.interview_calendar_view, name="interview_calendar"),
|
path
|
||||||
|
("jobs/<slug:slug>/calendar/", views.interview_calendar_view, name="interview_calendar"),
|
||||||
|
|
||||||
# Job Actions & Integrations
|
# Job Actions & Integrations
|
||||||
path("jobs/<slug:slug>/post-to-linkedin/", views.post_to_linkedin, name="post_to_linkedin"),
|
path("jobs/<slug:slug>/post-to-linkedin/", views.post_to_linkedin, name="post_to_linkedin"),
|
||||||
|
|||||||
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();
|
lucide.createIcons();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>h
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -4,291 +4,498 @@
|
|||||||
{% block title %}{% trans "Application" %}-{{ job.title }}{% endblock %}
|
{% block title %}{% trans "Application" %}-{{ job.title }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<!-- Simple Header -->
|
||||||
|
<div class="page-header">
|
||||||
|
<div class="container">
|
||||||
|
<a href="{% url 'kaauh_career' %}" class="back-link">
|
||||||
|
<i data-lucide="arrow-left" class="icon-sm"></i>
|
||||||
|
<span>{% trans "All Positions" %}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="main-content">
|
||||||
|
<!-- Job Header -->
|
||||||
|
<div class="job-header">
|
||||||
|
<h1 class="job-title">{{ job.title }}</h1>
|
||||||
|
<div class="job-meta">
|
||||||
|
<span class="meta-item">
|
||||||
|
<i data-lucide="building-2" class="icon-sm"></i>
|
||||||
|
{{ job.department }}
|
||||||
|
</span>
|
||||||
|
<span class="meta-item">
|
||||||
|
<i data-lucide="map-pin" class="icon-sm"></i>
|
||||||
|
{{ job.get_location_display }}
|
||||||
|
</span>
|
||||||
|
<span class="meta-item">
|
||||||
|
<i data-lucide="briefcase" class="icon-sm"></i>
|
||||||
|
{{ job.get_job_type_display }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Job Details Grid -->
|
||||||
|
<div class="details-grid">
|
||||||
|
{% if job.salary_range %}
|
||||||
|
<div class="detail-item">
|
||||||
|
<div class="detail-label">{% trans "Salary" %}</div>
|
||||||
|
<div class="detail-value">{{ job.salary_range }}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="detail-item">
|
||||||
|
<div class="detail-label">{% trans "Deadline" %}</div>
|
||||||
|
<div class="detail-value">
|
||||||
|
{% if job.application_deadline %}
|
||||||
|
{{ job.application_deadline|date:"M d, Y" }}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Open" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detail-item">
|
||||||
|
<div class="detail-label">{% trans "Work Mode" %}</div>
|
||||||
|
<div class="detail-value">{{ job.workplace_type|default:"On-site" }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content Sections -->
|
||||||
|
{% if job.has_description_content %}
|
||||||
|
<section class="content-section">
|
||||||
|
<h2 class="section-heading">{% trans "About the Role" %}</h2>
|
||||||
|
<div class="section-content">
|
||||||
|
{{ job.description|safe }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if job.has_qualifications_content %}
|
||||||
|
<section class="content-section">
|
||||||
|
<h2 class="section-heading">{% trans "What We're Looking For" %}</h2>
|
||||||
|
<div class="section-content">
|
||||||
|
{{ job.qualifications|safe }}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<aside class="sidebar">
|
||||||
|
<div class="apply-card">
|
||||||
|
{% if job.form_template %}
|
||||||
|
{% if user.is_authenticated and already_applied %}
|
||||||
|
<div class="applied-badge">
|
||||||
|
<i data-lucide="check-circle" class="icon-md"></i>
|
||||||
|
<span>{% trans "Applied" %}</span>
|
||||||
|
</div>
|
||||||
|
<p class="apply-text">{% trans "Your application has been submitted" %}</p>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'application_submit_form' job.slug %}" class="apply-button">
|
||||||
|
{% trans "Apply for this Position" %}
|
||||||
|
<i data-lucide="arrow-right" class="icon-sm"></i>
|
||||||
|
</a>
|
||||||
|
{% if job.application_deadline %}
|
||||||
|
<p class="apply-deadline">
|
||||||
|
{% trans "Apply before" %} {{ job.application_deadline|date:"M d, Y" }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Info -->
|
||||||
|
<div class="info-card">
|
||||||
|
<h3 class="info-title">{% trans "Quick Info" %}</h3>
|
||||||
|
<div class="info-list">
|
||||||
|
{% if job.internal_job_id %}
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">{% trans "Job ID" %}</span>
|
||||||
|
<span class="info-value">{{ job.internal_job_id }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">{% trans "Posted" %}</span>
|
||||||
|
<span class="info-value">{{ job.created_at|date:"M d, Y" }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Apply -->
|
||||||
|
{% if job.form_template and not already_applied %}
|
||||||
|
<div class="mobile-apply">
|
||||||
|
<a href="{% url 'application_submit_form' job.slug %}" class="mobile-apply-btn">
|
||||||
|
{% trans "Apply Now" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* 1. LAYOUT & GRID */
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||||
.page-container {
|
|
||||||
max-width: 1200px;
|
:root {
|
||||||
margin: 2rem auto 5rem;
|
--red: #9d2235;
|
||||||
padding-inline: 1.5rem;
|
--red-dark: #7a1a29;
|
||||||
display: grid;
|
--gray-50: #fafafa;
|
||||||
grid-template-columns: 1fr 350px;
|
--gray-100: #f5f5f5;
|
||||||
gap: 2rem;
|
--gray-200: #e5e5e5;
|
||||||
align-items: start;
|
--gray-400: #a3a3a3;
|
||||||
|
--gray-600: #525252;
|
||||||
|
--gray-900: #171717;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 992px) {
|
* {
|
||||||
.page-container { grid-template-columns: 1fr; }
|
margin: 0;
|
||||||
.desktop-sidebar { display: none; }
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 2. STICKY TOP NAV */
|
body {
|
||||||
.job-nav {
|
font-family: 'Inter', -apple-system, sans-serif;
|
||||||
background-color: var(--temple-red);
|
color: var(--gray-900);
|
||||||
color: white;
|
background: #ffffff;
|
||||||
padding: 0.75rem 1.5rem;
|
line-height: 1.6;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
.page-wrapper {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
border-bottom: 1px solid var(--gray-200);
|
||||||
|
padding: 1.5rem 0;
|
||||||
|
background: #ffffff;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
font-weight: 700;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 3. CARDS & SECTIONS */
|
.container {
|
||||||
.content-card {
|
max-width: 75rem;
|
||||||
background: white;
|
margin: 0 auto;
|
||||||
border-radius: 1rem;
|
padding: 0 1.5rem;
|
||||||
border: 1px solid var(--border);
|
|
||||||
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-header {
|
.content-wrapper {
|
||||||
padding: 1.5rem;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body { padding: 1.5rem; }
|
|
||||||
|
|
||||||
/* 4. METADATA GRID */
|
|
||||||
.meta-section {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
grid-template-columns: 1fr;
|
||||||
gap: 1.25rem;
|
gap: 3rem;
|
||||||
background: #f9fafb;
|
padding: 3rem 0;
|
||||||
border: 1px solid var(--border);
|
}
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
@media (min-width: 1024px) {
|
||||||
|
.content-wrapper {
|
||||||
|
grid-template-columns: 1fr 22rem;
|
||||||
|
gap: 4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Back Link */
|
||||||
|
.back-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: var(--gray-600);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link:hover {
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Job Header */
|
||||||
|
.job-header {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.job-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1.5rem;
|
||||||
|
color: var(--gray-600);
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.meta-item {
|
.meta-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.5rem;
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--gray-text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta-item svg {
|
/* Details Grid */
|
||||||
width: 1.25rem;
|
.details-grid {
|
||||||
height: 1.25rem;
|
display: grid;
|
||||||
color: var(--temple-red);
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
flex-shrink: 0;
|
gap: 1.5rem;
|
||||||
|
padding: 2rem;
|
||||||
|
background: var(--gray-50);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
margin-bottom: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 5. PURE CSS ACCORDION */
|
.detail-item {
|
||||||
.accordion-item { border-bottom: 1px solid var(--border); }
|
|
||||||
.accordion-toggle { display: none; }
|
|
||||||
|
|
||||||
.accordion-header {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
gap: 0.25rem;
|
||||||
padding: 1.25rem;
|
}
|
||||||
cursor: pointer;
|
|
||||||
|
.detail-label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--gray-600);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-value {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Content Sections */
|
||||||
|
.content-section {
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-heading {
|
||||||
|
font-size: 1.5rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--temple-red-dark);
|
margin-bottom: 1.5rem;
|
||||||
transition: background 0.2s;
|
letter-spacing: -0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion-header:hover { background: var(--temple-cream); }
|
.section-content {
|
||||||
|
font-size: 1rem;
|
||||||
.accordion-content {
|
line-height: 1.75;
|
||||||
max-height: 0;
|
color: var(--gray-600);
|
||||||
overflow: hidden;
|
|
||||||
transition: max-height 0.3s ease-out;
|
|
||||||
color: #4b5563;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion-toggle:checked + .accordion-header + .accordion-content {
|
.section-content h1,
|
||||||
max-height: 2000px; /* Large enough for content */
|
.section-content h2,
|
||||||
padding: 1rem 1.25rem 2rem;
|
.section-content h3,
|
||||||
|
.section-content h4 {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gray-900);
|
||||||
|
margin: 2rem 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion-icon {
|
.section-content h2 { font-size: 1.25rem; }
|
||||||
transition: transform 0.3s;
|
.section-content h3 { font-size: 1.125rem; }
|
||||||
}
|
|
||||||
.accordion-toggle:checked + .accordion-header .accordion-icon {
|
.section-content p {
|
||||||
transform: rotate(180deg);
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 6. SIDEBAR & BUTTONS */
|
.section-content ul,
|
||||||
.sticky-sidebar {
|
.section-content ol {
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content strong {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-content a {
|
||||||
|
color: var(--red);
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar */
|
||||||
|
.sidebar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 80px;
|
top: 7rem;
|
||||||
|
align-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-apply {
|
.apply-card,
|
||||||
|
.info-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid var(--gray-200);
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply Button */
|
||||||
|
.apply-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
background: var(--temple-red);
|
|
||||||
color: white;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
text-decoration: none;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: none;
|
padding: 1rem 1.5rem;
|
||||||
transition: 0.2s;
|
background: var(--red);
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-apply:disabled { background: #9ca3af; cursor: not-allowed; }
|
.apply-button:hover {
|
||||||
.btn-apply:not(:disabled):hover { background: var(--temple-red-dark); }
|
background: var(--red-dark);
|
||||||
|
}
|
||||||
|
|
||||||
/* 7. MOBILE FOOTER */
|
.apply-text {
|
||||||
.mobile-apply-bar {
|
font-size: 0.875rem;
|
||||||
|
color: var(--gray-600);
|
||||||
|
margin-top: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.apply-deadline {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--gray-600);
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Applied Badge */
|
||||||
|
.applied-badge {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--gray-50);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Info Card */
|
||||||
|
.info-title {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: var(--gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Apply */
|
||||||
|
.mobile-apply {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.mobile-apply {
|
||||||
|
display: block;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: white;
|
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-top: 1px solid var(--border);
|
background: #ffffff;
|
||||||
box-shadow: 0 -4px 6px -1px rgba(0,0,0,0.1);
|
border-top: 1px solid var(--gray-200);
|
||||||
z-index: 1000;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 993px) { .mobile-apply-bar { display: none; } }
|
.mobile-apply-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--red);
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding-bottom: 5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icons */
|
||||||
|
.icon-sm {
|
||||||
|
width: 1.125rem;
|
||||||
|
height: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-md {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.job-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 1.5rem;
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
padding: 2rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<nav class="job-nav">
|
<script>
|
||||||
<div style="max-width: 1200px; margin: 0 auto;">
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
{% trans "Job Overview" %}
|
if (typeof lucide !== 'undefined') {
|
||||||
</div>
|
lucide.createIcons();
|
||||||
</nav>
|
}
|
||||||
|
});
|
||||||
<div class="page-container">
|
</script>
|
||||||
<main>
|
|
||||||
<article class="content-card">
|
|
||||||
<header class="card-header">
|
|
||||||
<h1 style="font-size: 1.875rem; font-weight: 800; color: var(--temple-red-dark); margin: 0;">
|
|
||||||
{{ job.title }}
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
<h4 style="font-size: 1.1rem; font-weight: 700; color: #6b7280; border-bottom: 2px solid #f3f4f6; padding-bottom: 0.5rem; margin-bottom: 1.5rem;">
|
|
||||||
{% trans "Summary" %}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<section class="meta-section">
|
|
||||||
{% if job.salary_range %}
|
|
||||||
<div class="meta-item">
|
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
||||||
<strong>{% trans "Salary:" %}</strong> <span style="color: var(--temple-red); font-weight: 700;">{{ job.salary_range }}</span>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="meta-item">
|
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>
|
|
||||||
<strong>{% trans "Deadline:" %}</strong>
|
|
||||||
{% if job.application_deadline %}
|
|
||||||
{{ job.application_deadline|date:"M d, Y" }}
|
|
||||||
{% if job.is_expired %}<span style="color: #dc2626; font-size: 0.7rem; font-weight: 800;">(EXPIRED)</span>{% endif %}
|
|
||||||
{% else %}{% trans "Ongoing" %}{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="meta-item">
|
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg>
|
|
||||||
<strong>{% trans "Type:" %}</strong> {{ job.get_job_type_display }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="meta-item">
|
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path></svg>
|
|
||||||
<strong>{% trans "Location:" %}</strong> {{ job.get_location_display }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="meta-item">
|
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"></path></svg>
|
|
||||||
<strong>{% trans "Department:" %}</strong> {{ job.department|default:"N/A" }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="meta-item">
|
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 20l4-16m2 16l4-16M6 9h14M4 15h14"></path></svg>
|
|
||||||
<strong>{% trans "ID:" %}</strong> {{ job.internal_job_id|default:"N/A" }}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div class="accordion-container">
|
|
||||||
|
|
||||||
{% if job.has_description_content %}
|
|
||||||
<div class="accordion-item">
|
|
||||||
<input type="checkbox" id="acc1" class="accordion-toggle" checked>
|
|
||||||
<label for="acc1" class="accordion-header">
|
|
||||||
<span style="display: flex; align-items: center; gap: 0.75rem;">
|
|
||||||
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
|
||||||
{% trans "Job Description" %}
|
|
||||||
</span>
|
|
||||||
<svg class="accordion-icon heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
|
||||||
</label>
|
|
||||||
<div class="accordion-content">
|
|
||||||
<div class="wysiwyg-content">{{ job.description|safe }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if job.has_qualifications_content %}
|
|
||||||
<div class="accordion-item">
|
|
||||||
<input type="checkbox" id="acc2" class="accordion-toggle">
|
|
||||||
<label for="acc2" class="accordion-header">
|
|
||||||
<span style="display: flex; align-items: center; gap: 0.75rem;">
|
|
||||||
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222"></path></svg>
|
|
||||||
{% trans "Qualifications" %}
|
|
||||||
</span>
|
|
||||||
<svg class="accordion-icon heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
|
|
||||||
</label>
|
|
||||||
<div class="accordion-content">
|
|
||||||
<div class="wysiwyg-content">{{ job.qualifications|safe }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<aside class="desktop-sidebar">
|
|
||||||
<div class="content-card sticky-sidebar">
|
|
||||||
<header class="card-header">
|
|
||||||
<h5 style="margin: 0; font-weight: 800; color: var(--temple-red); display: flex; align-items: center; gap: 0.5rem;">
|
|
||||||
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"></path></svg>
|
|
||||||
{% trans "Ready to Apply?" %}
|
|
||||||
</h5>
|
|
||||||
</header>
|
|
||||||
<div class="card-body" style="text-align: center;">
|
|
||||||
<p style="color: #6b7280; font-size: 0.875rem; margin-bottom: 1.5rem;">
|
|
||||||
{% trans "Review the details before submitting your application." %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{% if job.form_template %}
|
|
||||||
{% if user.is_authenticated and already_applied %}
|
|
||||||
<button class="btn-apply" disabled>
|
|
||||||
{% trans "Already Applied" %}
|
|
||||||
</button>
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'application_submit_form' job.slug %}" class="btn-apply">
|
|
||||||
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path></svg>
|
|
||||||
{% trans "Apply Now" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if job.form_template %}
|
|
||||||
<div class="mobile-apply-bar">
|
|
||||||
{% if user.is_authenticated and already_applied %}
|
|
||||||
<button class="btn-apply" disabled style="width: 100%;">{% trans "Already Applied" %}</button>
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'application_submit_form' job.slug %}" class="btn-apply">{% trans "Apply for this Position" %}</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -9,270 +9,89 @@
|
|||||||
<title>{% trans "Careers" %} - {% block title %}{% trans "Application Form" %}{% endblock %}</title>
|
<title>{% trans "Careers" %} - {% block title %}{% trans "Application Form" %}{% endblock %}</title>
|
||||||
|
|
||||||
<link rel="icon" type="image/png" href="{% static 'image/favicon/favicon-32x32.png'%}">
|
<link rel="icon" type="image/png" href="{% static 'image/favicon/favicon-32x32.png'%}">
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<style>
|
<script>
|
||||||
:root {
|
tailwind.config = {
|
||||||
--temple-red: #9d2235;
|
theme: {
|
||||||
--temple-red-dark: #7a1a29;
|
extend: {
|
||||||
--temple-dark: #1a1a1a;
|
colors: {
|
||||||
--temple-cream: #f8f7f2;
|
'temple-red': '#9d2235',
|
||||||
--white: #ffffff;
|
'temple-red-dark': '#7a1a29',
|
||||||
--light-bg: #f8f9fa;
|
'temple-cream': '#f8f7f2',
|
||||||
--gray-text: #6c757d;
|
|
||||||
--danger: #dc3545;
|
|
||||||
--border: #d0d7de;
|
|
||||||
--shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
|
||||||
--nav-height: 70px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
||||||
background-color: var(--light-bg);
|
|
||||||
color: var(--temple-dark);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- NAVIGATION --- */
|
|
||||||
.navbar {
|
|
||||||
height: var(--nav-height);
|
|
||||||
background: var(--white);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-inline: 2rem;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-brand {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
text-decoration: none;
|
|
||||||
gap: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--temple-red);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
}
|
||||||
|
</script>
|
||||||
.nav-brand-icon {
|
|
||||||
width: 45px;
|
|
||||||
height: 45px;
|
|
||||||
background: var(--temple-red);
|
|
||||||
color: white;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--gray-text);
|
|
||||||
font-weight: 500;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link:hover { color: var(--temple-red); }
|
|
||||||
|
|
||||||
/* --- BUTTONS --- */
|
|
||||||
.btn-lang {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--temple-red);
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-lang:hover { background: var(--temple-cream); }
|
|
||||||
|
|
||||||
/* --- DROPDOWN --- */
|
|
||||||
.dropdown { position: relative; }
|
|
||||||
|
|
||||||
.dropdown-trigger {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 2px solid var(--temple-cream);
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-avatar-placeholder {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--temple-red);
|
|
||||||
color: white;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-menu {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + 10px);
|
|
||||||
inset-inline-end: 0;
|
|
||||||
background: var(--white);
|
|
||||||
min-width: 260px;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 1100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown.active .dropdown-menu { display: block; }
|
|
||||||
|
|
||||||
.dropdown-header {
|
|
||||||
padding: 1rem;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #444;
|
|
||||||
gap: 0.75rem;
|
|
||||||
transition: background 0.2s;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
width: 100%;
|
|
||||||
text-align: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item:hover { background: var(--temple-cream); }
|
|
||||||
.dropdown-item svg { width: 20px; height: 20px; color: var(--gray-text); }
|
|
||||||
.dropdown-item.logout { color: var(--danger); }
|
|
||||||
.dropdown-item.logout svg { color: var(--danger); }
|
|
||||||
|
|
||||||
/* --- ALERTS --- */
|
|
||||||
.alert-container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 1rem auto;
|
|
||||||
padding-inline: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background: #fef2f2;
|
|
||||||
color: var(--temple-red-dark);
|
|
||||||
border-inline-start: 4px solid var(--temple-red);
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heroicon { width: 20px; height: 20px; flex-shrink: 0; }
|
|
||||||
|
|
||||||
/* --- RESPONSIVE --- */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.navbar { padding-inline: 1rem; }
|
|
||||||
.nav-brand span { display: none; }
|
|
||||||
.d-mobile-none { display: none; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="font-sans bg-gray-50 text-gray-900">
|
||||||
|
|
||||||
<nav class="navbar">
|
<!-- Navigation -->
|
||||||
<a href="{% url 'kaauh_career' %}" class="nav-brand">
|
<nav class="h-[70px] bg-white border-b border-gray-200 flex items-center px-4 md:px-8 sticky top-0 z-50 justify-between">
|
||||||
<div class="nav-brand-icon">
|
<a href="{% url 'kaauh_career' %}" class="flex items-center gap-3 font-bold text-temple-red text-xl no-underline">
|
||||||
|
<div class="w-11 h-11 bg-temple-red text-white rounded-lg flex items-center justify-center text-2xl font-extrabold">
|
||||||
<svg style="width:24px; height:24px;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg style="width:24px; height:24px;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span>{% trans "Careers" %}</span>
|
<span class="hidden md:inline">{% trans "Careers" %}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="nav-actions">
|
<div class="flex items-center gap-4">
|
||||||
{% if request.resolver_match.url_name != "kaauh_career" %}
|
{% if request.resolver_match.url_name != "kaauh_career" %}
|
||||||
<a href="{% url 'kaauh_career' %}" class="nav-link d-mobile-none">{% trans "Careers" %}</a>
|
<a href="{% url 'kaauh_career' %}" class="text-gray-500 font-medium transition hover:text-temple-red no-underline hidden md:block">
|
||||||
|
{% trans "Careers" %}
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form action="{% url 'set_language' %}" method="post">
|
<form action="{% url 'set_language' %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||||
{% if LANGUAGE_CODE == 'en' %}
|
{% if LANGUAGE_CODE == 'en' %}
|
||||||
<button name="language" value="ar" class="btn-lang" type="submit">
|
<button name="language" value="ar" class="bg-transparent border border-gray-300 px-4 py-2 rounded-lg cursor-pointer flex items-center gap-2 font-medium text-temple-red transition hover:bg-temple-cream" type="submit">
|
||||||
🇸🇦 <span class="d-mobile-none">العربية</span>
|
🇸🇦 <span class="hidden md:inline">العربية</span>
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button name="language" value="en" class="btn-lang" type="submit">
|
<button name="language" value="en" class="bg-transparent border border-gray-300 px-4 py-2 rounded-lg cursor-pointer flex items-center gap-2 font-medium text-temple-red transition hover:bg-temple-cream" type="submit">
|
||||||
🇺🇸 <span class="d-mobile-none">English</span>
|
🇺🇸 <span class="hidden md:inline">English</span>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<div class="dropdown" id="userDropdown">
|
<div class="dropdown relative" id="userDropdown">
|
||||||
<button class="dropdown-trigger" onclick="toggleDropdown()">
|
<button class="bg-none border-none cursor-pointer flex items-center" onclick="toggleDropdown()">
|
||||||
{% if user.profile_image %}
|
{% if user.profile_image %}
|
||||||
<img src="{{ user.profile_image.url }}" class="profile-avatar">
|
<img src="{{ user.profile_image.url }}" class="w-10 h-10 rounded-full object-cover border-2 border-temple-cream">
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="profile-avatar-placeholder">
|
<div class="w-10 h-10 rounded-full bg-temple-red text-white flex items-center justify-center font-bold">
|
||||||
{{ user.username|first|upper }}
|
{{ user.username|first|upper }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="dropdown-menu">
|
<div class="dropdown-menu absolute top-[50px] end-0 bg-white min-w-[260px] rounded-xl shadow-lg border border-gray-200 overflow-hidden z-[60] hidden">
|
||||||
<div class="dropdown-header">
|
<div class="p-4 border-b border-gray-200 flex items-center gap-3">
|
||||||
<div>
|
<div>
|
||||||
<div style="font-weight: 600;">{{ user.get_full_name|default:user.username }}</div>
|
<div class="font-semibold text-gray-900">{{ user.get_full_name|default:user.username }}</div>
|
||||||
<div style="font-size: 0.8rem; color: var(--gray-text);">{{ user.email|truncatechars:22 }}</div>
|
<div class="text-sm text-gray-500">{{ user.email|truncatechars:22 }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="{% url 'applicant_portal_dashboard' %}" class="dropdown-item">
|
<a href="{% url 'applicant_portal_dashboard' %}" class="flex items-center p-3 text-gray-700 gap-3 transition hover:bg-temple-cream border-none bg-none w-full text-left cursor-pointer text-base no-underline">
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
|
<svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path></svg>
|
||||||
{% trans "Dashboard" %}
|
{% trans "Dashboard" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'user_detail' request.user.pk %}" class="dropdown-item">
|
<a href="{% url 'user_detail' request.user.pk %}" class="flex items-center p-3 text-gray-700 gap-3 transition hover:bg-temple-cream border-none bg-none w-full text-left cursor-pointer text-base no-underline">
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
<svg class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||||
{% trans "My Profile" %}
|
{% trans "My Profile" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<form method="post" action="{% url 'account_logout'%}">
|
<form method="post" action="{% url 'account_logout'%}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="dropdown-item logout">
|
<button type="submit" class="flex items-center p-3 text-red-500 gap-3 transition hover:bg-temple-cream border-none bg-none w-full text-left cursor-pointer text-base">
|
||||||
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path></svg>
|
<svg class="w-5 h-5 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1"></path></svg>
|
||||||
{% trans "Sign Out" %}
|
{% trans "Sign Out" %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -282,15 +101,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<!-- Alert Messages -->
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="alert-container">
|
<div class="max-w-5xl mx-auto my-4 px-4">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert">
|
<div class="p-4 rounded-lg bg-red-50 text-temple-red-dark border-l-4 border-temple-red mb-3 flex items-center justify-between">
|
||||||
<div style="display: flex; align-items: center; gap: 0.75rem;">
|
<div class="flex items-center gap-3">
|
||||||
<svg class="heroicon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
<svg class="w-5 h-5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
<button onclick="this.parentElement.remove()" style="background:none; border:none; cursor:pointer; font-size: 1.25rem;">×</button>
|
<button onclick="this.parentElement.remove()" class="bg-none border-none cursor-pointer text-xl">×</button>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
@ -300,6 +120,10 @@
|
|||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dropdown.active .dropdown-menu { display: block; }
|
||||||
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function toggleDropdown() {
|
function toggleDropdown() {
|
||||||
document.getElementById('userDropdown').classList.toggle('active');
|
document.getElementById('userDropdown').classList.toggle('active');
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
{% load logo_tags %}
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
@ -93,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Safe area padding for notched devices */
|
/* Safe area padding for notched devices */
|
||||||
@supports (padding: env(safe-area-inset-bottom)) {
|
@supports (padding: env(dQrgH6E6C$$!@9safe-area-inset-bottom)) {
|
||||||
.safe-area-padding-bottom {
|
.safe-area-padding-bottom {
|
||||||
padding-bottom: calc(1.5rem + env(safe-area-inset-bottom));
|
padding-bottom: calc(1.5rem + env(safe-area-inset-bottom));
|
||||||
}
|
}
|
||||||
@ -272,10 +273,12 @@
|
|||||||
<div class="flex flex-col h-full">
|
<div class="flex flex-col h-full">
|
||||||
<!-- Sidebar Header -->
|
<!-- Sidebar Header -->
|
||||||
<div class="p-4 sm:p-6 flex items-center gap-3 text-white sidebar-header safe-area-padding-top">
|
<div class="p-4 sm:p-6 flex items-center gap-3 text-white sidebar-header safe-area-padding-top">
|
||||||
<div class="bg-temple-red p-2 rounded-lg shrink-0">
|
{% comment %} <div class="bg-temple-red p-2 rounded-lg shrink-0">
|
||||||
<i data-lucide="shield-check" class="w-5 h-5 sm:w-6 sm:h-6 text-white"></i>
|
<i data-lucide="shield-check" class="w-5 h-5 sm:w-6 sm:h-6 text-white"></i>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-lg sm:text-xl font-bold tracking-tight sidebar-text">{% trans "ATS" %}</span>
|
<span class="text-lg sm:text-xl font-bold tracking-tight sidebar-text">{% trans "ATS" %}</span> {% endcomment %}
|
||||||
|
{% logo_ats height="40" %}
|
||||||
|
{% logo_tenhal_ats height="40" %}
|
||||||
<button onclick="closeMobileSidebar()"
|
<button onclick="closeMobileSidebar()"
|
||||||
class="lg:hidden ml-auto text-gray-400 hover:text-white transition p-2 -mr-2"
|
class="lg:hidden ml-auto text-gray-400 hover:text-white transition p-2 -mr-2"
|
||||||
aria-label="{% trans 'Close menu' %}">
|
aria-label="{% trans 'Close menu' %}">
|
||||||
|
|||||||
@ -4,156 +4,40 @@
|
|||||||
|
|
||||||
{% block title %}Create Form Template - ATS{% endblock %}
|
{% block title %}Create Form Template - ATS{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* ================================================= */
|
|
||||||
/* THEME VARIABLES AND GLOBAL STYLES */
|
|
||||||
/* ================================================= */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Primary Color Overrides */
|
|
||||||
.text-primary { color: var(--kaauh-teal) !important; }
|
|
||||||
|
|
||||||
/* Main Action Button Style */
|
|
||||||
.btn-main-action, .btn-primary {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.6rem 1.2rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover, .btn-primary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card enhancements */
|
|
||||||
.card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card Header Theming */
|
|
||||||
.card-header {
|
|
||||||
background-color: #f0f8ff !important; /* Light blue tint for header */
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
border-radius: 0.75rem 0.75rem 0 0;
|
|
||||||
}
|
|
||||||
.card-header h3 {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.card-header .fas {
|
|
||||||
color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form styling */
|
|
||||||
.form-control {
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus {
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-check-input:checked {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modal styling */
|
|
||||||
.modal-content {
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header {
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
padding: 1.25rem 1.5rem;
|
|
||||||
background-color: #f0f8ff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-footer {
|
|
||||||
border-top: 1px solid var(--kaauh-border);
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Error message styling */
|
|
||||||
.invalid-feedback {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
font-size: 0.875em;
|
|
||||||
color: #dc3545;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control.is-invalid {
|
|
||||||
border-color: #dc3545;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Success message styling */
|
|
||||||
.alert-success {
|
|
||||||
background-color: #d4edda;
|
|
||||||
border-color: #c3e6cb;
|
|
||||||
color: #155724;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container py-4">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4 pb-2 border-bottom border-primary">
|
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
<h1 class="h3 mb-0 fw-bold text-primary">
|
<div class="flex-1">
|
||||||
<i class="fas fa-file-alt me-2"></i>Create Form Template
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="file-plus-2" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
Create Form Template
|
||||||
</h1>
|
</h1>
|
||||||
<a href="{% url 'form_templates_list' %}" class="btn btn-secondary btn-sm">
|
</div>
|
||||||
<i class="fas fa-arrow-left me-1"></i>Back to Templates
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> Back to Templates
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="flex justify-center">
|
||||||
<div class="col-lg-8">
|
<div class="w-full max-w-4xl">
|
||||||
<div class="card">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="card-header">
|
<div class="bg-gradient-to-br from-temple-red/5 to-transparent border-b border-gray-100 px-6 py-4">
|
||||||
<h3 class="h5 mb-0"><i class="fas fa-plus-circle me-2"></i>New Form Template</h3>
|
<h3 class="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<i data-lucide="plus-circle" class="w-5 h-5 text-temple-red"></i> New Form Template
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-4">
|
<div class="p-6">
|
||||||
<form method="post" id="createFormTemplate">
|
<form method="post" id="createFormTemplate" class="space-y-6">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<div class="d-flex justify-content-between">
|
<div class="flex flex-col sm:flex-row justify-between gap-3 pt-4 border-t border-gray-200">
|
||||||
<a href="{% url 'form_templates_list' %}" class="btn btn-secondary">
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-6 py-2.5 rounded-xl text-sm font-medium transition">
|
||||||
<i class="fas fa-times me-1"></i>Cancel
|
<i data-lucide="x" class="w-4 h-4"></i> Cancel
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="btn btn-main-action">
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-save me-1"></i>Create Template
|
<i data-lucide="save" class="w-4 h-4"></i> Create Template
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -165,9 +49,9 @@
|
|||||||
|
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
<div class="fixed top-4 right-4 z-50 bg-emerald-100 border border-emerald-200 text-emerald-800 px-4 py-3 rounded-xl shadow-lg flex items-center gap-3 animate-in slide-in-from-right">
|
||||||
<i class="fas fa-check-circle me-2"></i>{{ message }}
|
<i data-lucide="check-circle" class="w-5 h-5 text-emerald-600"></i>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<span class="text-sm font-medium">{{ message }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -175,7 +59,6 @@
|
|||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
<script>
|
<script>
|
||||||
// Add form validation
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const form = document.getElementById('createFormTemplate');
|
const form = document.getElementById('createFormTemplate');
|
||||||
|
|
||||||
@ -203,6 +86,8 @@
|
|||||||
this.classList.remove('is-invalid');
|
this.classList.remove('is-invalid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lucide.createIcons();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -4,7 +4,7 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="documentType" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Document Type" %}</label>
|
<label for="documentType" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Document Type" %}</label>
|
||||||
<select class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition" id="documentType" name="document_type" required>
|
<select class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 bg-white focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" id="documentType" name="document_type" required>
|
||||||
<option value="">{% trans "Select document type" %}</option>
|
<option value="">{% trans "Select document type" %}</option>
|
||||||
<option value="resume">{% trans "Resume" %}</option>
|
<option value="resume">{% trans "Resume" %}</option>
|
||||||
<option value="cover_letter">{% trans "Cover Letter" %}</option>
|
<option value="cover_letter">{% trans "Cover Letter" %}</option>
|
||||||
@ -17,13 +17,13 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="documentDescription" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Description" %}</label>
|
<label for="documentDescription" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Description" %}</label>
|
||||||
<textarea class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition resize-none" id="documentDescription" name="description" rows="3" placeholder="{% trans 'Optional description...' %}"></textarea>
|
<textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition resize-none" id="documentDescription" name="description" rows="3" placeholder="{% trans 'Optional description...' %}"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label for="documentFile" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Choose File" %}</label>
|
<label for="documentFile" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Choose File" %}</label>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<input type="file" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition file:mr-4 file:py-1.5 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-kaauh-blue file:text-white hover:file:bg-[#004f57]" id="documentFile" name="file" accept=".pdf,.doc,.docx,.jpg,.png" required>
|
<input type="file" class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition file:mr-4 file:py-1.5 file:px-4 file:rounded-lg file:border-0 file:text-sm file:font-semibold file:bg-temple-red file:text-white hover:file:bg-[#7a1a29]" id="documentFile" name="file" accept=".pdf,.doc,.docx,.jpg,.png" required>
|
||||||
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
|
<div class="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
|
||||||
<i data-lucide="upload" class="w-5 h-5 text-gray-400"></i>
|
<i data-lucide="upload" class="w-5 h-5 text-gray-400"></i>
|
||||||
</div>
|
</div>
|
||||||
@ -34,17 +34,13 @@
|
|||||||
|
|
||||||
<div class="mt-6 pt-4 border-t border-gray-200">
|
<div class="mt-6 pt-4 border-t border-gray-200">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onclick="this.closest('form').parentElement.parentElement.classList.add('hidden')"
|
class="modal-cancel-btn inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2.5 rounded-xl text-sm transition"
|
||||||
class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2.5 rounded-xl text-sm transition">
|
data-modal="documentUploadModal">
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md ml-2">
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md ml-2">
|
||||||
<i data-lucide="upload" class="w-4 h-4"></i>
|
<i data-lucide="upload" class="w-4 h-4"></i>
|
||||||
{% trans "Upload" %}
|
{% trans "Upload" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script>
|
|
||||||
lucide.createIcons();
|
|
||||||
</script>
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -5,85 +5,56 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{ form.title }} - Embed</title>
|
<title>{{ form.title }} - Embed</title>
|
||||||
|
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Tailwind CSS -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<!-- Font Awesome -->
|
<script src="https://unpkg.com/lucide@latest"></script>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
|
||||||
|
<script>
|
||||||
|
tailwind.config = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
'temple-red': '#9d2235',
|
||||||
|
'temple-dark': '#1a1a1a',
|
||||||
|
'temple-cream': '#f8f7f2',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');
|
||||||
body {
|
body {
|
||||||
background: #f8f9fa;
|
font-family: 'Inter', sans-serif;
|
||||||
margin: 0;
|
-webkit-font-smoothing: antialiased;
|
||||||
padding: 0;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-container {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 0;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-form-wrapper {
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-form {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-form .card {
|
|
||||||
border: none;
|
|
||||||
border-radius: 12px;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-form .card-header {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
border: none;
|
|
||||||
padding: 30px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-form .card-body {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-info {
|
|
||||||
background: white;
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 30px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-block {
|
.code-block {
|
||||||
background: #2d3748;
|
background: #2d3748;
|
||||||
color: #e2e8f0;
|
color: #e2e8f0;
|
||||||
border-radius: 8px;
|
border-radius: 0.5rem;
|
||||||
padding: 20px;
|
padding: 1.25rem;
|
||||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
font-size: 14px;
|
font-size: 0.875rem;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 15px;
|
margin-top: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.copy-btn {
|
.copy-btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 10px;
|
top: 0.625rem;
|
||||||
right: 10px;
|
right: 0.625rem;
|
||||||
background: #4a5568;
|
background: #4a5568;
|
||||||
border: none;
|
border: none;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 8px 12px;
|
padding: 0.5rem 0.75rem;
|
||||||
border-radius: 6px;
|
border-radius: 0.375rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
font-size: 0.75rem;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,170 +65,81 @@
|
|||||||
.copy-btn.copied {
|
.copy-btn.copied {
|
||||||
background: #48bb78;
|
background: #48bb78;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-iframe {
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 100%;
|
|
||||||
height: 600px;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tabs .nav-link {
|
|
||||||
border: none;
|
|
||||||
background: #f7fafc;
|
|
||||||
color: #4a5568;
|
|
||||||
padding: 12px 24px;
|
|
||||||
margin-right: 8px;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tabs .nav-link.active {
|
|
||||||
background: white;
|
|
||||||
color: #2d3748;
|
|
||||||
border-bottom: 2px solid #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-tabs .nav-link:hover {
|
|
||||||
background: #edf2f7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-list {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-list li {
|
|
||||||
padding: 8px 0;
|
|
||||||
border-bottom: 1px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-list li:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-list i {
|
|
||||||
color: #48bb78;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dimensions-info {
|
|
||||||
background: #f7fafc;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-options {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-option {
|
|
||||||
background: white;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 15px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-option:hover {
|
|
||||||
border-color: #667eea;
|
|
||||||
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.responsive-option.active {
|
|
||||||
border-color: #667eea;
|
|
||||||
background: #f0f4ff;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="bg-gray-100 text-gray-800 min-h-screen">
|
||||||
<div class="embed-container">
|
<div class="p-4 md:p-6">
|
||||||
<div class="embed-form-wrapper">
|
<div class="max-w-5xl mx-auto">
|
||||||
<div class="embed-form">
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="embed-info">
|
<div class="bg-white rounded-2xl shadow-md p-6 mb-6">
|
||||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
<div>
|
<div class="flex-1">
|
||||||
<h1 class="h2 mb-2">
|
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 mb-2 flex items-center gap-2">
|
||||||
<i class="fas fa-code text-primary"></i> Embed Form
|
<i data-lucide="code-2" class="w-7 h-7 text-temple-red"></i>
|
||||||
|
Embed Form
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">Get the embed code for "{{ form.title }}"</p>
|
<p class="text-gray-600">Get embed code for "{{ form.title }}"</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'form_preview' form.id %}" target="_blank" class="btn btn-outline-primary">
|
<a href="{% url 'form_preview' form.id %}" target="_blank" class="inline-flex items-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-4 py-2.5 rounded-xl transition font-medium">
|
||||||
<i class="fas fa-external-link-alt"></i> Preview Form
|
<i data-lucide="external-link" class="w-4 h-4"></i> Preview Form
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- Stats -->
|
||||||
<div class="row text-center mb-4">
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-center mb-6">
|
||||||
<div class="col-md-3">
|
<div class="p-4 bg-gray-50 rounded-xl">
|
||||||
<div class="p-3">
|
<div class="text-2xl font-bold text-temple-red">{{ form.submissions.count }}</div>
|
||||||
<h4 class="text-primary mb-1">{{ form.submissions.count }}</h4>
|
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Submissions</div>
|
||||||
<small class="text-muted">Submissions</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-4 bg-gray-50 rounded-xl">
|
||||||
|
<div class="text-2xl font-bold text-emerald-600">{{ form.structure.wizards|length|default:0 }}</div>
|
||||||
|
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Steps</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="p-4 bg-gray-50 rounded-xl">
|
||||||
<div class="p-3">
|
<div class="text-2xl font-bold text-blue-600">
|
||||||
<h4 class="text-success mb-1">{{ form.structure.wizards|length|default:0 }}</h4>
|
|
||||||
<small class="text-muted">Steps</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="p-3">
|
|
||||||
<h4 class="text-info mb-1">
|
|
||||||
{% if form.structure.wizards %}
|
{% if form.structure.wizards %}
|
||||||
{{ form.structure.wizards.0.fields|length|default:0 }}
|
{{ form.structure.wizards.0.fields|length|default:0 }}
|
||||||
{% else %}
|
{% else %}
|
||||||
0
|
0
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h4>
|
|
||||||
<small class="text-muted">Fields</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Fields</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="p-4 bg-gray-50 rounded-xl">
|
||||||
<div class="p-3">
|
<div class="text-2xl font-bold text-amber-600">{{ form.created_at|date:"M d" }}</div>
|
||||||
<h4 class="text-warning mb-1">{{ form.created_at|date:"M d" }}</h4>
|
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Created</div>
|
||||||
<small class="text-muted">Created</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<ul class="nav nav-tabs" id="embedTabs" role="tablist">
|
<div class="flex border-b border-gray-200 mb-6">
|
||||||
<li class="nav-item" role="presentation">
|
<button class="tab-btn px-6 py-3 font-medium text-gray-600 hover:text-temple-red border-b-2 border-transparent hover:border-temple-red transition flex items-center gap-2 active"
|
||||||
<button class="nav-link active" id="iframe-tab" data-bs-toggle="tab" data-bs-target="#iframe" type="button" role="tab">
|
data-tab="iframe"
|
||||||
<i class="fas fa-globe"></i> iFrame
|
onclick="switchTab('iframe')">
|
||||||
|
<i data-lucide="globe" class="w-4 h-4"></i> iFrame
|
||||||
</button>
|
</button>
|
||||||
</li>
|
<button class="tab-btn px-6 py-3 font-medium text-gray-600 hover:text-temple-red border-b-2 border-transparent hover:border-temple-red transition flex items-center gap-2"
|
||||||
<li class="nav-item" role="presentation">
|
data-tab="popup"
|
||||||
<button class="nav-link" id="popup-tab" data-bs-toggle="tab" data-bs-target="#popup" type="button" role="tab">
|
onclick="switchTab('popup')">
|
||||||
<i class="fas fa-external-link-alt"></i> Popup
|
<i data-lucide="external-link" class="w-4 h-4"></i> Popup
|
||||||
</button>
|
</button>
|
||||||
</li>
|
<button class="tab-btn px-6 py-3 font-medium text-gray-600 hover:text-temple-red border-b-2 border-transparent hover:border-temple-red transition flex items-center gap-2"
|
||||||
<li class="nav-item" role="presentation">
|
data-tab="inline"
|
||||||
<button class="nav-link" id="inline-tab" data-bs-toggle="tab" data-bs-target="#inline" type="button" role="tab">
|
onclick="switchTab('inline')">
|
||||||
<i class="fas fa-code"></i> Inline
|
<i data-lucide="code" class="w-4 h-4"></i> Inline
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content" id="embedTabsContent">
|
<!-- Tab Contents -->
|
||||||
|
<div id="tab-content">
|
||||||
<!-- iFrame Tab -->
|
<!-- iFrame Tab -->
|
||||||
<div class="tab-pane fade show active" id="iframe" role="tabpanel">
|
<div class="tab-pane" id="iframe-pane">
|
||||||
<h5 class="mb-3">iFrame Embed Code</h5>
|
<h5 class="text-lg font-semibold text-gray-900 mb-3">iFrame Embed Code</h5>
|
||||||
<p class="text-muted">Embed this form directly into your website using an iframe.</p>
|
<p class="text-gray-600 mb-4">Embed this form directly into your website using an iframe.</p>
|
||||||
|
|
||||||
<div class="code-block">
|
<div class="code-block">
|
||||||
<button class="copy-btn" onclick="copyToClipboard(this, 'iframe-code')">
|
<button class="copy-btn" onclick="copyToClipboard(this, 'iframe-code')">
|
||||||
<i class="fas fa-copy"></i> Copy
|
<i data-lucide="copy" class="w-3 h-3"></i> Copy
|
||||||
</button>
|
</button>
|
||||||
<code id="iframe-code"><iframe src="{{ request.build_absolute_uri }}{% url 'form_preview' form.id %}?embed=true"
|
<code id="iframe-code"><iframe src="{{ request.build_absolute_uri }}{% url 'form_preview' form.id %}?embed=true"
|
||||||
width="100%"
|
width="100%"
|
||||||
@ -266,35 +148,38 @@
|
|||||||
style="border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);"></iframe></code>
|
style="border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);"></iframe></code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dimensions-info">
|
<div class="bg-gray-50 rounded-xl p-4 mt-4">
|
||||||
<h6 class="mb-3">Responsive Options</h6>
|
<h6 class="font-semibold text-gray-900 mb-3">Responsive Options</h6>
|
||||||
<div class="responsive-options">
|
<div class="space-y-2">
|
||||||
<div class="responsive-option active" onclick="selectResponsive(this, 'iframe', 'fixed')">
|
<div class="responsive-option active bg-white border-2 border-temple-red p-4 rounded-xl cursor-pointer hover:shadow-md transition"
|
||||||
<strong>Fixed Height:</strong> 600px
|
onclick="selectResponsive(this, 'iframe', 'fixed')">
|
||||||
<div class="text-muted small">Best for most websites</div>
|
<div class="font-semibold">Fixed Height: 600px</div>
|
||||||
|
<div class="text-sm text-gray-500">Best for most websites</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="responsive-option" onclick="selectResponsive(this, 'iframe', 'responsive')">
|
<div class="responsive-option bg-white border-2 border-gray-200 p-4 rounded-xl cursor-pointer hover:border-temple-red transition"
|
||||||
<strong>Responsive:</strong> Auto height
|
onclick="selectResponsive(this, 'iframe', 'responsive')">
|
||||||
<div class="text-muted small">Adjusts to content height</div>
|
<div class="font-semibold">Responsive: Auto height</div>
|
||||||
|
<div class="text-sm text-gray-500">Adjusts to content height</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="responsive-option" onclick="selectResponsive(this, 'iframe', 'full')">
|
<div class="responsive-option bg-white border-2 border-gray-200 p-4 rounded-xl cursor-pointer hover:border-temple-red transition"
|
||||||
<strong>Full Screen:</strong> 100vh
|
onclick="selectResponsive(this, 'iframe', 'full')">
|
||||||
<div class="text-muted small">Takes full viewport height</div>
|
<div class="font-semibold">Full Screen: 100vh</div>
|
||||||
|
<div class="text-sm text-gray-500">Takes full viewport height</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Popup Tab -->
|
<!-- Popup Tab -->
|
||||||
<div class="tab-pane fade" id="popup" role="tabpanel">
|
<div class="tab-pane hidden" id="popup-pane">
|
||||||
<h5 class="mb-3">Popup Embed Code</h5>
|
<h5 class="text-lg font-semibold text-gray-900 mb-3">Popup Embed Code</h5>
|
||||||
<p class="text-muted">Add a button or link that opens the form in a modal popup.</p>
|
<p class="text-gray-600 mb-4">Add a button or link that opens to form in a modal popup.</p>
|
||||||
|
|
||||||
<div class="code-block">
|
<div class="code-block">
|
||||||
<button class="copy-btn" onclick="copyToClipboard(this, 'popup-code')">
|
<button class="copy-btn" onclick="copyToClipboard(this, 'popup-code')">
|
||||||
<i class="fas fa-copy"></i> Copy
|
<i data-lucide="copy" class="w-3 h-3"></i> Copy
|
||||||
</button>
|
</button>
|
||||||
<code id="popup-code"><button onclick="openFormPopup()" class="btn btn-primary">
|
<code id="popup-code"><button onclick="openFormPopup()" class="bg-temple-red hover:bg-[#7a1a29] text-white px-6 py-3 rounded-xl font-medium transition">
|
||||||
Open Form
|
Open Form
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -319,51 +204,59 @@ function openFormPopup() {
|
|||||||
</script></code>
|
</script></code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-6">
|
||||||
<h6>Customization Options</h6>
|
<h6 class="font-semibold text-gray-900 mb-3">Customization Options</h6>
|
||||||
<ul class="feature-list">
|
<ul class="space-y-2">
|
||||||
<li><i class="fas fa-check"></i> Custom button text and styling</li>
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
<li><i class="fas fa-check"></i> Trigger on page load or scroll</li>
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Custom button text and styling
|
||||||
<li><i class="fas fa-check"></i> Custom modal dimensions</li>
|
</li>
|
||||||
<li><i class="fas fa-check"></i> Close on outside click</li>
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Trigger on page load or scroll
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Custom modal dimensions
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Close on outside click
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Inline Tab -->
|
<!-- Inline Tab -->
|
||||||
<div class="tab-pane fade" id="inline" role="tabpanel">
|
<div class="tab-pane hidden" id="inline-pane">
|
||||||
<h5 class="mb-3">Inline Embed Code</h5>
|
<h5 class="text-lg font-semibold text-gray-900 mb-3">Inline Embed Code</h5>
|
||||||
<p class="text-muted">Embed the form HTML directly into your page for maximum customization.</p>
|
<p class="text-gray-600 mb-4">Embed form HTML directly into your page for maximum customization.</p>
|
||||||
|
|
||||||
<div class="alert alert-info">
|
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4 mb-4 flex items-start gap-3">
|
||||||
<i class="fas fa-info-circle"></i> <strong>Note:</strong> This option requires more technical knowledge but offers the best integration.
|
<i data-lucide="info" class="w-5 h-5 text-blue-600 shrink-0 mt-0.5"></i>
|
||||||
|
<div>
|
||||||
|
<strong class="text-blue-900">Note:</strong> This option requires more technical knowledge but offers best integration.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="code-block">
|
<div class="code-block">
|
||||||
<button class="copy-btn" onclick="copyToClipboard(this, 'inline-code')">
|
<button class="copy-btn" onclick="copyToClipboard(this, 'inline-code')">
|
||||||
<i class="fas fa-copy"></i> Copy
|
<i data-lucide="copy" class="w-3 h-3"></i> Copy
|
||||||
</button>
|
</button>
|
||||||
<code id="inline-code"><!-- Form CSS -->
|
<code id="inline-code"><!-- Form CSS -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.tailwindcss.com" rel="stylesheet">
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- Form Container -->
|
<!-- Form Container -->
|
||||||
<div id="form-{{ form.id }}">
|
<div id="form-{{ form.id }}">
|
||||||
<div class="text-center py-5">
|
<div class="text-center py-12">
|
||||||
<div class="spinner-border text-primary" role="status"></div>
|
<div class="inline-block w-8 h-8 border-4 border-temple-red border-t-transparent rounded-full animate-spin"></div>
|
||||||
<p class="mt-3">Loading form...</p>
|
<p class="mt-4 text-gray-600">Loading form...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form Scripts -->
|
<!-- Form Scripts -->
|
||||||
<script src="https://unpkg.com/preact@10.19.3/dist/preact.umd.js"></script>
|
|
||||||
<script src="https://unpkg.com/htm@3.1.1/dist/htm.umd.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
// Load form data and render
|
// Load form data and render
|
||||||
fetch('/recruitment/api/forms/{{ form.id }}/load/')
|
fetch('/recruitment/api/forms/{{ form.id }}/load/')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
// Render form using the form structure
|
// Render form using to form structure
|
||||||
console.log('Form data:', data);
|
console.log('Form data:', data);
|
||||||
// Implement form rendering logic here
|
// Implement form rendering logic here
|
||||||
})
|
})
|
||||||
@ -371,13 +264,21 @@ function openFormPopup() {
|
|||||||
</script></code>
|
</script></code>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-3">
|
<div class="mt-6">
|
||||||
<h6>Benefits of Inline Embed</h6>
|
<h6 class="font-semibold text-gray-900 mb-3">Benefits of Inline Embed</h6>
|
||||||
<ul class="feature-list">
|
<ul class="space-y-2">
|
||||||
<li><i class="fas fa-check"></i> Full control over styling</li>
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
<li><i class="fas fa-check"></i> Better SEO integration</li>
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Full control over styling
|
||||||
<li><i class="fas fa-check"></i> Faster initial load</li>
|
</li>
|
||||||
<li><i class="fas fa-check"></i> Custom form handling</li>
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Better SEO integration
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Faster initial load
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Custom form handling
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -385,39 +286,59 @@ function openFormPopup() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Preview Section -->
|
<!-- Preview Section -->
|
||||||
<div class="card">
|
<div class="bg-white rounded-2xl shadow-md overflow-hidden mt-6">
|
||||||
<div class="card-header">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<h5 class="mb-0">
|
<h5 class="font-semibold flex items-center gap-2">
|
||||||
<i class="fas fa-eye"></i> Live Preview
|
<i data-lucide="eye" class="w-5 h-5"></i> Live Preview
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="p-0">
|
||||||
<iframe src="{% url 'form_preview' form.id %}?embed=true"
|
<iframe src="{% url 'form_preview' form.id %}?embed=true"
|
||||||
class="preview-iframe"
|
class="w-full border border-gray-200 rounded-xl"
|
||||||
|
style="height: 600px;"
|
||||||
frameborder="0">
|
frameborder="0">
|
||||||
</iframe>
|
</iframe>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Bootstrap JS -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
|
function switchTab(tabName) {
|
||||||
|
// Update tab buttons
|
||||||
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
|
btn.classList.remove('active', 'text-temple-red', 'border-temple-red');
|
||||||
|
btn.classList.add('text-gray-600', 'border-transparent');
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeBtn = document.querySelector(`[data-tab="${tabName}"]`);
|
||||||
|
activeBtn.classList.add('active', 'text-temple-red', 'border-temple-red');
|
||||||
|
activeBtn.classList.remove('text-gray-600', 'border-transparent');
|
||||||
|
|
||||||
|
// Show/hide tab panes
|
||||||
|
document.querySelectorAll('.tab-pane').forEach(pane => {
|
||||||
|
pane.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById(`${tabName}-pane`).classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
function copyToClipboard(button, elementId) {
|
function copyToClipboard(button, elementId) {
|
||||||
const element = document.getElementById(elementId);
|
const element = document.getElementById(elementId);
|
||||||
const text = element.textContent;
|
const text = element.textContent;
|
||||||
|
|
||||||
navigator.clipboard.writeText(text).then(function() {
|
navigator.clipboard.writeText(text).then(function() {
|
||||||
const originalText = button.innerHTML;
|
const originalText = button.innerHTML;
|
||||||
button.innerHTML = '<i class="fas fa-check"></i> Copied!';
|
button.innerHTML = '<i data-lucide="check" class="w-3 h-3"></i> Copied!';
|
||||||
button.classList.add('copied');
|
button.classList.add('copied');
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
button.innerHTML = originalText;
|
button.innerHTML = originalText;
|
||||||
button.classList.remove('copied');
|
button.classList.remove('copied');
|
||||||
|
lucide.createIcons();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}).catch(function(err) {
|
}).catch(function(err) {
|
||||||
console.error('Failed to copy text: ', err);
|
console.error('Failed to copy text: ', err);
|
||||||
@ -431,12 +352,14 @@ function openFormPopup() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
button.innerHTML = '<i class="fas fa-check"></i> Copied!';
|
button.innerHTML = '<i data-lucide="check" class="w-3 h-3"></i> Copied!';
|
||||||
button.classList.add('copied');
|
button.classList.add('copied');
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
button.innerHTML = '<i class="fas fa-copy"></i> Copy';
|
button.innerHTML = originalText;
|
||||||
button.classList.remove('copied');
|
button.classList.remove('copied');
|
||||||
|
lucide.createIcons();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Fallback copy failed: ', err);
|
console.error('Fallback copy failed: ', err);
|
||||||
@ -450,13 +373,15 @@ function openFormPopup() {
|
|||||||
// Remove active class from all options in this tab
|
// Remove active class from all options in this tab
|
||||||
const container = element.closest('.tab-pane');
|
const container = element.closest('.tab-pane');
|
||||||
container.querySelectorAll('.responsive-option').forEach(opt => {
|
container.querySelectorAll('.responsive-option').forEach(opt => {
|
||||||
opt.classList.remove('active');
|
opt.classList.remove('active', 'border-temple-red');
|
||||||
|
opt.classList.add('border-gray-200');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add active class to selected option
|
// Add active class to selected option
|
||||||
element.classList.add('active');
|
element.classList.add('active', 'border-temple-red');
|
||||||
|
element.classList.remove('border-gray-200');
|
||||||
|
|
||||||
// Update the embed code based on selection
|
// Update embed code based on selection
|
||||||
updateEmbedCode(type, option);
|
updateEmbedCode(type, option);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -489,14 +414,6 @@ function openFormPopup() {
|
|||||||
document.getElementById('iframe-code').textContent = code;
|
document.getElementById('iframe-code').textContent = code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize tooltips
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
|
||||||
const tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
|
||||||
return new bootstrap.Tooltip(tooltipTriggerEl);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -3,30 +3,35 @@
|
|||||||
{% block title %}Forms - University ATS{% endblock %}
|
{% block title %}Forms - University ATS{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="row">
|
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
<div class="col-12">
|
<div class="flex-1">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
<h1><i class="fas fa-wpforms"></i> Forms</h1>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
<a href="{% url 'form_builder' %}" class="btn btn-primary">
|
<i data-lucide="layout-template" class="w-8 h-8 text-temple-red"></i>
|
||||||
<i class="fas fa-plus"></i> Create New Form
|
</div>
|
||||||
|
Forms
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'form_builder' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="plus" class="w-5 h-5"></i> Create New Form
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search and Filter -->
|
<!-- Search and Filter -->
|
||||||
<div class="card mb-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-6">
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<form method="get" class="row g-3">
|
<form method="get" class="flex flex-col md:flex-row gap-4">
|
||||||
<div class="col-md-8">
|
<div class="flex-1">
|
||||||
<div class="input-group">
|
<div class="relative">
|
||||||
<input type="text" class="form-control" name="search" placeholder="Search forms..." value="{{ request.GET.search }}">
|
<input type="text" class="w-full px-4 py-2.5 pr-12 border border-gray-300 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" name="search" placeholder="Search forms..." value="{{ request.GET.search }}">
|
||||||
<button class="btn btn-outline-secondary" type="submit">
|
<button class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-temple-red transition" type="submit">
|
||||||
<i class="fas fa-search"></i>
|
<i data-lucide="search" class="w-5 h-5"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="md:w-64">
|
||||||
<select class="form-select" name="sort">
|
<select class="w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 bg-white focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition" name="sort">
|
||||||
<option value="-created_at">Latest First</option>
|
<option value="-created_at">Latest First</option>
|
||||||
<option value="created_at">Oldest First</option>
|
<option value="created_at">Oldest First</option>
|
||||||
<option value="title">Title (A-Z)</option>
|
<option value="title">Title (A-Z)</option>
|
||||||
@ -39,104 +44,102 @@
|
|||||||
|
|
||||||
<!-- Forms List -->
|
<!-- Forms List -->
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{% for form in page_obj %}
|
{% for form in page_obj %}
|
||||||
<div class="col-lg-4 col-md-6 mb-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
|
||||||
<div class="card h-100">
|
<div class="p-6">
|
||||||
<div class="card-body">
|
<div class="flex justify-between items-start mb-3">
|
||||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
<h5 class="text-lg font-bold text-gray-900 mb-1">{{ form.title }}</h5>
|
||||||
<h5 class="card-title mb-1">{{ form.title }}</h5>
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-bold uppercase bg-emerald-100 text-emerald-800">Active</span>
|
||||||
<span class="badge bg-success">Active</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="card-text text-muted small">
|
<p class="text-sm text-gray-600 mb-4 line-clamp-2">
|
||||||
{{ form.description|truncatewords:15 }}
|
{{ form.description|truncatewords:15 }}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="mb-2">
|
<div class="space-y-2 mb-4">
|
||||||
<small class="text-muted">
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
<i class="fas fa-user"></i> {{ form.created_by.username }}
|
<i data-lucide="user" class="w-4 h-4 text-gray-400"></i>
|
||||||
</small>
|
{{ form.created_by.username }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
<div class="mb-2">
|
<i data-lucide="calendar" class="w-4 h-4 text-gray-400"></i>
|
||||||
<small class="text-muted">
|
{{ form.created_at|date:"M d, Y" }}
|
||||||
<i class="fas fa-calendar"></i> {{ form.created_at|date:"M d, Y" }}
|
|
||||||
</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
<div class="mb-3">
|
<i data-lucide="bar-chart-3" class="w-4 h-4 text-gray-400"></i>
|
||||||
<small class="text-muted">
|
{{ form.submissions.count }} submissions
|
||||||
<i class="fas fa-chart-bar"></i> {{ form.submissions.count }} submissions
|
|
||||||
</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer bg-transparent">
|
</div>
|
||||||
<div class="btn-group w-100" role="group">
|
<div class="border-t border-gray-100 p-4 bg-gray-50/50">
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
{% if form.created_by == user %}
|
{% if form.created_by == user %}
|
||||||
<a href="{% url 'edit_form' form.slug %}" class="btn btn-sm btn-outline-warning">
|
<a href="{% url 'edit_form' form.slug %}" class="inline-flex items-center gap-1 border border-amber-300 text-amber-700 hover:bg-amber-50 px-3 py-2 rounded-lg text-xs font-semibold transition">
|
||||||
<i class="fas fa-edit"></i> Edit
|
<i data-lucide="edit-3" class="w-3 h-3"></i> Edit
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'form_preview' form.slug %}" class="btn btn-sm btn-outline-primary" target="_blank">
|
<a href="{% url 'form_preview' form.slug %}" class="inline-flex items-center gap-1 border border-temple-red text-temple-red hover:bg-temple-red/10 px-3 py-2 rounded-lg text-xs font-semibold transition" target="_blank">
|
||||||
<i class="fas fa-eye"></i> Preview
|
<i data-lucide="eye" class="w-3 h-3"></i> Preview
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'form_embed' form.slug %}" class="btn btn-sm btn-outline-secondary" target="_blank">
|
<a href="{% url 'form_embed' form.slug %}" class="inline-flex items-center gap-1 border border-gray-300 text-gray-600 hover:bg-gray-100 px-3 py-2 rounded-lg text-xs font-semibold transition" target="_blank">
|
||||||
<i class="fas fa-code"></i> Embed
|
<i data-lucide="code-2" class="w-3 h-3"></i> Embed
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'form_submissions' form.slug %}" class="btn btn-sm btn-outline-info">
|
<a href="{% url 'form_submissions' form.slug %}" class="inline-flex items-center gap-1 border border-blue-300 text-blue-700 hover:bg-blue-50 px-3 py-2 rounded-lg text-xs font-semibold transition">
|
||||||
<i class="fas fa-list"></i> Submissions
|
<i data-lucide="list" class="w-3 h-3"></i> Submissions
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
{% if page_obj.has_other_pages %}
|
{% if page_obj.has_other_pages %}
|
||||||
<nav aria-label="Forms pagination">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 mt-6">
|
||||||
<ul class="pagination justify-content-center">
|
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
Showing page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Forms pagination" class="flex items-center gap-2">
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<li class="page-item">
|
<a href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="First">
|
||||||
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">First</a>
|
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||||
</li>
|
</a>
|
||||||
<li class="page-item">
|
<a href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Previous">
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Previous</a>
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
</li>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<li class="page-item active">
|
<span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||||
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
|
{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
||||||
</li>
|
</span>
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<li class="page-item">
|
<a href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Next">
|
||||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Next</a>
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||||
</li>
|
</a>
|
||||||
<li class="page-item">
|
<a href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Last">
|
||||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Last</a>
|
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||||
</li>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-5">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||||
<i class="fas fa-wpforms fa-3x text-muted mb-3"></i>
|
<i data-lucide="layout-template" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
<h3>No forms found</h3>
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">No forms found</h3>
|
||||||
<p class="text-muted">Create your first form to get started.</p>
|
<p class="text-gray-500 mb-6">Create your first form to get started.</p>
|
||||||
<a href="{% url 'form_builder' %}" class="btn btn-primary">Create Form</a>
|
<a href="{% url 'form_builder' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="plus" class="w-5 h-5"></i> Create Form
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<script>
|
<script>
|
||||||
// Add any interactive JavaScript here if needed
|
lucide.createIcons();
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -4,59 +4,60 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if not is_embed %}
|
{% if not is_embed %}
|
||||||
<div class="container-fluid">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<div class="row">
|
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
<div class="col-12">
|
<div class="flex-1">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
<h1><i class="fas fa-wpforms"></i> Form Preview</h1>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
<div>
|
<i data-lucide="layout-template" class="w-8 h-8 text-temple-red"></i>
|
||||||
<a href="{% url 'form_list' %}" class="btn btn-outline-secondary me-2">
|
|
||||||
<i class="fas fa-arrow-left"></i> Back to Forms
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'form_embed' form.id %}" class="btn btn-outline-primary" target="_blank">
|
|
||||||
<i class="fas fa-code"></i> Get Embed Code
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
Form Preview
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<a href="{% url 'form_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> Back to Forms
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'form_embed' form.id %}" class="inline-flex items-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-4 py-2.5 rounded-xl text-sm transition font-medium" target="_blank">
|
||||||
|
<i data-lucide="code-2" class="w-4 h-4"></i> Get Embed Code
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Form Preview Container -->
|
<!-- Form Preview Container -->
|
||||||
<div class="{% if is_embed %}embed-container{% else %}container-fluid{% endif %}">
|
<div class="{% if is_embed %}bg-gray-100 min-h-screen{% else %}max-w-7xl mx-auto px-4 sm:px-6 lg:px-8{% endif %}">
|
||||||
<div class="{% if is_embed %}embed-form-wrapper{% else %}row justify-content-center{% endif %}">
|
<div class="{% if is_embed %}{% else %}flex justify-center{% endif %}">
|
||||||
<div class="{% if is_embed %}embed-form{% else %}col-lg-8 col-md-10{% endif %}">
|
<div class="{% if is_embed %}w-full{% else %}w-full max-w-4xl{% endif %}">
|
||||||
<!-- Form Header -->
|
<!-- Form Header -->
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-4 overflow-hidden">
|
||||||
<div class="card-header bg-primary text-white">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-6">
|
||||||
<h3 class="mb-1">{{ form.title }}</h3>
|
<h3 class="text-2xl font-bold mb-2">{{ form.title }}</h3>
|
||||||
{% if form.description %}
|
{% if form.description %}
|
||||||
<p class="mb-0 opacity-90">{{ form.description }}</p>
|
<p class="text-white/90 mb-0">{{ form.description }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-3">
|
||||||
<small class="text-muted">
|
<span class="text-sm text-gray-600 flex items-center gap-2">
|
||||||
<i class="fas fa-chart-bar"></i> {{ submission_count }} submissions
|
<i data-lucide="bar-chart-3" class="w-4 h-4"></i> {{ submission_count }} submissions
|
||||||
</small>
|
</span>
|
||||||
<small class="text-muted">
|
<span class="text-sm text-gray-600 flex items-center gap-2">
|
||||||
<i class="fas fa-calendar"></i> Created {{ form.created_at|date:"M d, Y" }}
|
<i data-lucide="calendar" class="w-4 h-4"></i> Created {{ form.created_at|date:"M d, Y" }}
|
||||||
</small>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Live Form Preview -->
|
<!-- Live Form Preview -->
|
||||||
<div class="card shadow-sm">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="card-body p-0">
|
<div class="p-0">
|
||||||
<div id="form-preview-container">
|
<div id="form-preview-container">
|
||||||
<!-- Form will be rendered here by Preact -->
|
<!-- Form will be rendered here by Preact -->
|
||||||
<div class="text-center py-5">
|
<div class="text-center py-12">
|
||||||
<div class="spinner-border text-primary" role="status">
|
<div class="inline-block w-12 h-12 border-4 border-temple-red border-t-transparent rounded-full animate-spin"></div>
|
||||||
<span class="visually-hidden">Loading form...</span>
|
<p class="mt-4 text-gray-500">Loading form preview...</p>
|
||||||
</div>
|
|
||||||
<p class="mt-3 text-muted">Loading form preview...</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -64,41 +65,35 @@
|
|||||||
|
|
||||||
<!-- Form Analytics (only shown if not embedded) -->
|
<!-- Form Analytics (only shown if not embedded) -->
|
||||||
{% if not is_embed %}
|
{% if not is_embed %}
|
||||||
<div class="card shadow-sm mt-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mt-6 overflow-hidden">
|
||||||
<div class="card-header">
|
<div class="border-b border-gray-100 p-6">
|
||||||
<h5><i class="fas fa-chart-line"></i> Form Analytics</h5>
|
<h5 class="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<i data-lucide="line-chart" class="w-5 h-5 text-temple-red"></i> Form Analytics
|
||||||
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<div class="row">
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||||
<div class="col-md-3">
|
<div class="text-center p-4 bg-gray-50 rounded-xl">
|
||||||
<div class="text-center">
|
<h4 class="text-2xl font-bold text-temple-red">{{ submission_count }}</h4>
|
||||||
<h4 class="text-primary">{{ submission_count }}</h4>
|
<span class="text-sm text-gray-600 mt-1 block">Total Submissions</span>
|
||||||
<small class="text-muted">Total Submissions</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-center p-4 bg-gray-50 rounded-xl">
|
||||||
|
<h4 class="text-2xl font-bold text-emerald-600">{{ form.created_at|timesince }}</h4>
|
||||||
|
<span class="text-sm text-gray-600 mt-1 block">Time Created</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div class="text-center p-4 bg-gray-50 rounded-xl">
|
||||||
<div class="text-center">
|
<h4 class="text-2xl font-bold text-blue-600">{{ form.structure.wizards|length|default:0 }}</h4>
|
||||||
<h4 class="text-success">{{ form.created_at|timesince }}</h4>
|
<span class="text-sm text-gray-600 mt-1 block">Form Steps</span>
|
||||||
<small class="text-muted">Time Created</small>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="text-center p-4 bg-gray-50 rounded-xl">
|
||||||
<div class="col-md-3">
|
<h4 class="text-2xl font-bold text-amber-600">
|
||||||
<div class="text-center">
|
|
||||||
<h4 class="text-info">{{ form.structure.wizards|length|default:0 }}</h4>
|
|
||||||
<small class="text-muted">Form Steps</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="text-center">
|
|
||||||
<h4 class="text-warning">
|
|
||||||
{% if form.structure.wizards %}
|
{% if form.structure.wizards %}
|
||||||
{{ form.structure.wizards.0.fields|length|default:0 }}
|
{{ form.structure.wizards.0.fields|length|default:0 }}
|
||||||
{% else %}
|
{% else %}
|
||||||
0
|
0
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h4>
|
</h4>
|
||||||
<small class="text-muted">First Step Fields</small>
|
<span class="text-sm text-gray-600 mt-1 block">First Step Fields</span>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -109,41 +104,47 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Success Modal -->
|
<!-- Success Modal -->
|
||||||
<div class="modal fade" id="successModal" tabindex="-1" aria-labelledby="successModalLabel" aria-hidden="true">
|
<div id="successModal" class="fixed inset-0 z-50 hidden">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="absolute inset-0 bg-black/50 transition-opacity" onclick="closeModal('successModal')"></div>
|
||||||
<div class="modal-content">
|
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||||
<div class="modal-header bg-success text-white">
|
<div class="relative bg-white rounded-2xl shadow-xl max-w-md w-full p-6">
|
||||||
<h5 class="modal-title" id="successModalLabel">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<i class="fas fa-check-circle"></i> Form Submitted Successfully!
|
<h5 class="text-xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<i data-lucide="check-circle" class="w-6 h-6 text-emerald-500"></i> Form Submitted Successfully!
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" onclick="closeModal('successModal')" class="text-gray-400 hover:text-gray-600 transition">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="mb-6">
|
||||||
<p class="mb-0">Thank you for submitting the form. Your response has been recorded.</p>
|
<p class="text-gray-600 mb-0">Thank you for submitting this form. Your response has been recorded.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="flex gap-3">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" onclick="closeModal('successModal')" class="flex-1 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">Close</button>
|
||||||
<button type="button" class="btn btn-primary" onclick="resetForm()">Submit Another Response</button>
|
<button type="button" onclick="resetForm(); closeModal('successModal');" class="flex-1 bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2.5 rounded-xl text-sm transition font-medium">Submit Another Response</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error Modal -->
|
<!-- Error Modal -->
|
||||||
<div class="modal fade" id="errorModal" tabindex="-1" aria-labelledby="errorModalLabel" aria-hidden="true">
|
<div id="errorModal" class="fixed inset-0 z-50 hidden">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="absolute inset-0 bg-black/50 transition-opacity" onclick="closeModal('errorModal')"></div>
|
||||||
<div class="modal-content">
|
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||||
<div class="modal-header bg-danger text-white">
|
<div class="relative bg-white rounded-2xl shadow-xl max-w-md w-full p-6">
|
||||||
<h5 class="modal-title" id="errorModalLabel">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<i class="fas fa-exclamation-triangle"></i> Submission Error
|
<h5 class="text-xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<i data-lucide="alert-triangle" class="w-6 h-6 text-red-500"></i> Submission Error
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" onclick="closeModal('errorModal')" class="text-gray-400 hover:text-gray-600 transition">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="mb-6">
|
||||||
<p class="mb-0" id="errorMessage">An error occurred while submitting the form. Please try again.</p>
|
<p class="text-gray-600 mb-0" id="errorMessage">An error occurred while submitting the form. Please try again.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="flex gap-3">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" onclick="closeModal('errorModal')" class="flex-1 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -153,36 +154,249 @@
|
|||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
{% if is_embed %}
|
{% if is_embed %}
|
||||||
<style>
|
<style>
|
||||||
.embed-container {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-form-wrapper {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-form {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-form .card {
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embed-form .card-header {
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: #f8f9fa;
|
background: #f3f4f6;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<style>
|
||||||
|
/* Form Styles */
|
||||||
|
.form-label {
|
||||||
|
@apply block text-sm font-semibold text-gray-700 mb-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
@apply w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control.is-invalid {
|
||||||
|
@apply border-red-500 focus:border-red-500 focus:ring-red-500/20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
@apply w-full px-4 py-2.5 border border-gray-300 rounded-xl text-sm text-gray-900 bg-white focus:outline-none focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select.is-invalid {
|
||||||
|
@apply border-red-500 focus:border-red-500 focus:ring-red-500/20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check {
|
||||||
|
@apply flex items-start gap-3 mb-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check-input {
|
||||||
|
@apply mt-1 w-4 h-4 text-temple-red border-gray-300 rounded focus:ring-2 focus:ring-temple-red/20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-check-label {
|
||||||
|
@apply text-sm text-gray-700 cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invalid-feedback {
|
||||||
|
@apply text-red-500 text-sm mt-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
@apply text-red-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
@apply text-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-text {
|
||||||
|
@apply text-sm text-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress Bar */
|
||||||
|
.progress {
|
||||||
|
@apply w-full bg-gray-200 rounded-full overflow-hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
@apply bg-temple-red h-full transition-all duration-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rating Stars */
|
||||||
|
.rating-star {
|
||||||
|
@apply cursor-pointer transition-transform hover:scale-110;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rating-star.active {
|
||||||
|
@apply text-amber-400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FilePond Custom Styles */
|
||||||
|
.filepond--root {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filepond--drop-label {
|
||||||
|
@apply text-gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filepond--label-action {
|
||||||
|
@apply text-temple-red font-semibold hover:text-temple-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button Styles */
|
||||||
|
.btn {
|
||||||
|
@apply inline-flex items-center justify-center gap-2 px-4 py-2.5 rounded-xl text-sm font-medium transition cursor-pointer border-0 outline-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
@apply bg-temple-red hover:bg-[#7a1a29] text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-secondary {
|
||||||
|
@apply border border-gray-300 text-gray-600 hover:bg-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-outline-secondary:disabled {
|
||||||
|
@apply opacity-50 cursor-not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
@apply border border-gray-300 text-gray-600 hover:bg-gray-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spinner */
|
||||||
|
.spinner-border {
|
||||||
|
@apply inline-block w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-border-sm {
|
||||||
|
@apply w-3 h-3 border-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-border.text-primary {
|
||||||
|
@apply border-temple-red text-temple-red;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Utility */
|
||||||
|
.visually-hidden {
|
||||||
|
@apply absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-flex {
|
||||||
|
@apply flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.justify-content-between {
|
||||||
|
@apply justify-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-items-center {
|
||||||
|
@apply items-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-3 {
|
||||||
|
@apply mb-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-4 {
|
||||||
|
@apply mb-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.me-2 {
|
||||||
|
@apply me-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-1 {
|
||||||
|
@apply mt-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mt-4 {
|
||||||
|
@apply mt-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.py-5 {
|
||||||
|
@apply py-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-center {
|
||||||
|
@apply text-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-primary {
|
||||||
|
@apply text-temple-red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-success {
|
||||||
|
@apply text-emerald-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-info {
|
||||||
|
@apply text-blue-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-warning {
|
||||||
|
@apply text-amber-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-4x {
|
||||||
|
@apply text-5xl;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
@apply text-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
@apply fixed inset-0 z-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-dialog-centered {
|
||||||
|
@apply flex items-center justify-center min-h-screen p-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
@apply bg-white rounded-2xl shadow-xl max-w-md w-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
@apply flex items-center justify-between px-6 py-4 border-b border-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
@apply px-6 py-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
@apply flex items-center justify-end gap-3 px-6 py-4 border-t border-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
@apply text-lg font-bold text-gray-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close {
|
||||||
|
@apply text-gray-400 hover:text-gray-600 transition p-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-close-white {
|
||||||
|
@apply text-white hover:text-white/80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header.bg-success {
|
||||||
|
@apply bg-emerald-500 text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header.bg-danger {
|
||||||
|
@apply bg-red-500 text-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header.bg-primary {
|
||||||
|
@apply bg-temple-red text-white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
@ -440,7 +654,7 @@ class PreviewRatingField extends Component {
|
|||||||
<div class="rating-container">
|
<div class="rating-container">
|
||||||
${[1, 2, 3, 4, 5].map(n => html`
|
${[1, 2, 3, 4, 5].map(n => html`
|
||||||
<span class="rating-star ${value >= n ? 'active' : ''}"
|
<span class="rating-star ${value >= n ? 'active' : ''}"
|
||||||
style="font-size: 24px; color: ${value >= n ? '#ffc107' : '#ddd'}; cursor: pointer; margin-right: 5px;"
|
style="font-size: 24px; color: ${value >= n ? '#fbbf24' : '#ddd'}; cursor: pointer; margin-right: 5px;"
|
||||||
onClick=${() => onChange(field.id, n)}>
|
onClick=${() => onChange(field.id, n)}>
|
||||||
★
|
★
|
||||||
</span>
|
</span>
|
||||||
@ -590,16 +804,14 @@ class FormPreview extends Component {
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
this.setState({ isSubmitted: true });
|
this.setState({ isSubmitted: true });
|
||||||
const modal = new bootstrap.Modal(document.getElementById('successModal'));
|
openModal('successModal');
|
||||||
modal.show();
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.error || 'Submission failed');
|
throw new Error(result.error || 'Submission failed');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Submission error:', error);
|
console.error('Submission error:', error);
|
||||||
document.getElementById('errorMessage').textContent = error.message;
|
document.getElementById('errorMessage').textContent = error.message;
|
||||||
const modal = new bootstrap.Modal(document.getElementById('errorModal'));
|
openModal('errorModal');
|
||||||
modal.show();
|
|
||||||
} finally {
|
} finally {
|
||||||
this.setState({ isSubmitting: false });
|
this.setState({ isSubmitting: false });
|
||||||
}
|
}
|
||||||
@ -621,10 +833,12 @@ class FormPreview extends Component {
|
|||||||
|
|
||||||
if (this.state.isSubmitted) {
|
if (this.state.isSubmitted) {
|
||||||
return html`
|
return html`
|
||||||
<div class="text-center py-5">
|
<div class="text-center py-12">
|
||||||
<i class="fas fa-check-circle text-success fa-4x mb-3"></i>
|
<div class="inline-flex items-center justify-center w-16 h-16 bg-emerald-100 rounded-full mb-4">
|
||||||
<h3>Thank You!</h3>
|
<i data-lucide="check-circle" class="w-8 h-8 text-emerald-600"></i>
|
||||||
<p class="text-muted">Your form has been submitted successfully.</p>
|
</div>
|
||||||
|
<h3 class="text-2xl font-bold text-gray-900 mb-2">Thank You!</h3>
|
||||||
|
<p class="text-gray-500 mb-6">Your form has been submitted successfully.</p>
|
||||||
<button class="btn btn-primary" onClick=${this.resetForm}>
|
<button class="btn btn-primary" onClick=${this.resetForm}>
|
||||||
Submit Another Response
|
Submit Another Response
|
||||||
</button>
|
</button>
|
||||||
@ -633,10 +847,10 @@ class FormPreview extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="form-preview">
|
<div class="form-preview p-6">
|
||||||
<!-- Progress Bar -->
|
<!-- Progress Bar -->
|
||||||
${formStructure.settings && formStructure.settings.showProgress && this.getTotalWizards() > 1 ? html`
|
${formStructure.settings && formStructure.settings.showProgress && this.getTotalWizards() > 1 ? html`
|
||||||
<div class="progress mb-4" style="height: 6px;">
|
<div class="progress mb-6" style="height: 6px;">
|
||||||
<div class="progress-bar"
|
<div class="progress-bar"
|
||||||
style="width: ${this.getProgress()}%; transition: width 0.3s ease;">
|
style="width: ${this.getProgress()}%; transition: width 0.3s ease;">
|
||||||
</div>
|
</div>
|
||||||
@ -645,7 +859,7 @@ class FormPreview extends Component {
|
|||||||
|
|
||||||
<!-- Wizard Title -->
|
<!-- Wizard Title -->
|
||||||
${currentWizard ? html`
|
${currentWizard ? html`
|
||||||
<h4 class="mb-4">${currentWizard.title || 'Step ' + (this.state.currentWizardIndex + 1)}</h4>
|
<h4 class="text-xl font-bold text-gray-900 mb-6">${currentWizard.title || 'Step ' + (this.state.currentWizardIndex + 1)}</h4>
|
||||||
` : ''}
|
` : ''}
|
||||||
|
|
||||||
<!-- Form Fields -->
|
<!-- Form Fields -->
|
||||||
@ -661,25 +875,25 @@ class FormPreview extends Component {
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Navigation Buttons -->
|
<!-- Navigation Buttons -->
|
||||||
<div class="d-flex justify-content-between mt-4">
|
<div class="flex flex-col sm:flex-row justify-between items-center gap-3 mt-6">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-outline-secondary"
|
class="btn btn-outline-secondary w-full sm:w-auto"
|
||||||
onClick=${this.handlePrevious}
|
onClick=${this.handlePrevious}
|
||||||
disabled=${this.state.currentWizardIndex === 0 || this.state.isSubmitting}>
|
disabled=${this.state.currentWizardIndex === 0 || this.state.isSubmitting}>
|
||||||
<i class="fas fa-arrow-left"></i> Previous
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> Previous
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary w-full sm:w-auto"
|
||||||
onClick=${this.handleNext}
|
onClick=${this.handleNext}
|
||||||
disabled=${this.state.isSubmitting}>
|
disabled=${this.state.isSubmitting}>
|
||||||
${this.state.isSubmitting ? html`
|
${this.state.isSubmitting ? html`
|
||||||
<span class="spinner-border spinner-border-sm me-2" role="status"></span>
|
<span class="spinner-border spinner-border-sm mr-2"></span>
|
||||||
Submitting...
|
Submitting...
|
||||||
` : ''}
|
` : ''}
|
||||||
${this.state.currentWizardIndex === this.getTotalWizards() - 1 ?
|
${this.state.currentWizardIndex === this.getTotalWizards() - 1 ?
|
||||||
html`<i class="fas fa-check"></i> Submit` :
|
html`<i data-lucide="check" class="w-4 h-4"></i> Submit` :
|
||||||
html`Next <i class="fas fa-arrow-right"></i>`
|
html`Next <i data-lucide="arrow-right" class="w-4 h-4"></i>`
|
||||||
}
|
}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -688,6 +902,39 @@ class FormPreview extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Modal functions
|
||||||
|
function openModal(modalId) {
|
||||||
|
const modal = document.getElementById(modalId);
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
setTimeout(() => {
|
||||||
|
const backdrop = modal.querySelector('.absolute');
|
||||||
|
if (backdrop) backdrop.classList.remove('opacity-0');
|
||||||
|
const content = modal.querySelector('.relative.bg-white');
|
||||||
|
if (content) {
|
||||||
|
content.classList.remove('opacity-0', 'scale-95');
|
||||||
|
content.classList.add('opacity-100', 'scale-100');
|
||||||
|
}
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal(modalId) {
|
||||||
|
const modal = document.getElementById(modalId);
|
||||||
|
if (modal) {
|
||||||
|
const backdrop = modal.querySelector('.absolute');
|
||||||
|
if (backdrop) backdrop.classList.add('opacity-0');
|
||||||
|
const content = modal.querySelector('.relative.bg-white');
|
||||||
|
if (content) {
|
||||||
|
content.classList.remove('opacity-100', 'scale-100');
|
||||||
|
content.classList.add('opacity-0', 'scale-95');
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize FilePond plugins
|
// Initialize FilePond plugins
|
||||||
if (typeof FilePond !== 'undefined') {
|
if (typeof FilePond !== 'undefined') {
|
||||||
FilePond.registerPlugin(
|
FilePond.registerPlugin(
|
||||||
@ -696,11 +943,12 @@ if (typeof FilePond !== 'undefined') {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the form preview
|
// Render form preview
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const container = document.getElementById('form-preview-container');
|
const container = document.getElementById('form-preview-container');
|
||||||
if (container) {
|
if (container) {
|
||||||
render(html`<${FormPreview} />`, container);
|
render(html`<${FormPreview} />`, container);
|
||||||
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -709,7 +957,11 @@ window.resetForm = function() {
|
|||||||
const container = document.getElementById('form-preview-container');
|
const container = document.getElementById('form-preview-container');
|
||||||
if (container) {
|
if (container) {
|
||||||
render(html`<${FormPreview} />`, container);
|
render(html`<${FormPreview} />`, container);
|
||||||
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
window.closeModal = closeModal;
|
||||||
|
window.openModal = openModal;
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,201 +1,67 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load i18n static %}
|
{% load i18n static form_filters %}
|
||||||
|
|
||||||
{% load form_filters %}
|
|
||||||
|
|
||||||
{% block title %}{{ form.name }} - {% trans "Submission Details" %}{% endblock %}
|
{% block title %}{{ form.name }} - {% trans "Submission Details" %}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* ================================================= */
|
|
||||||
/* THEME VARIABLES AND GLOBAL STYLES */
|
|
||||||
/* ================================================= */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e; /* Primary */
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Primary Color Overrides */
|
|
||||||
.text-primary { color: var(--kaauh-teal) !important; }
|
|
||||||
.text-info { color: #17a2b8 !important; }
|
|
||||||
.text-success { color: #28a745 !important; }
|
|
||||||
.text-secondary { color: #6c757d !important; }
|
|
||||||
.bg-info { background-color: #17a2b8 !important; }
|
|
||||||
.bg-secondary { background-color: #6c757d !important; }
|
|
||||||
|
|
||||||
/* Card enhancements */
|
|
||||||
.card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
background-color: #f8f9fa; /* Light background */
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Action Button Style */
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary outline button */
|
|
||||||
.btn-outline-secondary {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ================================================= */
|
|
||||||
/* RESPONSES TABLE SPECIFIC STYLES (Horizontal Layout) */
|
|
||||||
/* ================================================= */
|
|
||||||
|
|
||||||
/* Main table container */
|
|
||||||
.table-submission {
|
|
||||||
margin-bottom: 0;
|
|
||||||
border: none;
|
|
||||||
table-layout: auto; /* Allow columns to resize based on content */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fixed first column (the row headers) */
|
|
||||||
.table-submission th:first-child,
|
|
||||||
.table-submission td:first-child {
|
|
||||||
background-color: #f0f4f7; /* Slightly darker than header */
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
width: 150px;
|
|
||||||
min-width: 150px;
|
|
||||||
position: sticky; /* Keep it visible when scrolling right */
|
|
||||||
left: 0;
|
|
||||||
z-index: 2;
|
|
||||||
border-right: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
.table-submission th:first-child {
|
|
||||||
top: 0; /* Important for sticky header/row-header intersection */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Field Label Header Row (Top Row) */
|
|
||||||
.table-submission thead th {
|
|
||||||
font-weight: 600;
|
|
||||||
background-color: #e9ecef; /* Light gray for headers */
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
vertical-align: middle;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-bottom: 2px solid var(--kaauh-teal); /* Highlight the bottom of the header */
|
|
||||||
padding: 0.75rem 0.5rem;
|
|
||||||
}
|
|
||||||
.table-submission thead th:not(:first-child) {
|
|
||||||
min-width: 200px; /* Give response columns space */
|
|
||||||
max-width: 300px;
|
|
||||||
white-space: normal;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Data Cells */
|
|
||||||
.table-submission tbody td {
|
|
||||||
vertical-align: top;
|
|
||||||
padding: 0.75rem 0.75rem;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Styling for multi-value responses */
|
|
||||||
.table-submission .badge {
|
|
||||||
font-weight: 500;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* File display */
|
|
||||||
.table-submission .fa-file {
|
|
||||||
color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.table-submission .btn-outline-primary {
|
|
||||||
color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<div class="container-fluid py-4">
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||||
<nav aria-label="breadcrumb">
|
<!-- Breadcrumb -->
|
||||||
<ol class="breadcrumb">
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
<li class="breadcrumb-item "><a href="{% url 'job_detail' submission.template.job.slug %}" class="text-secondary text-decoration-none">{% trans "Job Detail" %}</a></li>
|
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}" class="text-secondary text-decoration-none">{% trans "Form Template" %}</a></li>
|
<li><a href="{% url 'job_detail' submission.template.job.slug %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Job Detail" %}</a></li>
|
||||||
<li class="breadcrumb-item active" aria-current="page" style="
|
<li class="text-gray-400">/</li>
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
<li><a href="{% url 'form_builder' submission.template.pk%}" class="text-gray-500 hover:text-temple-red transition">{% trans "Form Template" %}</a></li>
|
||||||
font-weight: 600;
|
<li class="text-temple-red font-semibold">{% trans "Submission Details" %}</li>
|
||||||
">{% trans "Submission Details" %}</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="flex-1">
|
||||||
<h2 class="text-primary fw-bold">{% trans "Submission Details" %}</h2>
|
<h2 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary">
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
|
<i data-lucide="file-check-2" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Submission Details" %}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Submissions" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card shadow-sm mb-4">
|
<!-- Submission Metadata -->
|
||||||
<div class="card-header">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-6 overflow-hidden">
|
||||||
<h5 class="mb-0">{% trans "Submission Metadata" %}</h5>
|
<div class="border-b border-gray-100 px-6 py-4">
|
||||||
|
<h5 class="text-lg font-bold text-gray-900">{% trans "Submission Metadata" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body small">
|
<div class="p-6">
|
||||||
<div class="row g-3">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div class="col-md-4">
|
<div class="flex items-center gap-2">
|
||||||
<i class="fas fa-fingerprint me-2 text-primary"></i>
|
<i data-lucide="fingerprint" class="w-5 h-5 text-temple-red"></i>
|
||||||
<strong>{% trans "Submission ID:" %}</strong> <span class="text-secondary">{{ submission.id }}</span>
|
<span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Submission ID:" %}</strong> {{ submission.id }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="flex items-center gap-2">
|
||||||
<i class="fas fa-calendar-check me-2 text-primary"></i>
|
<i data-lucide="calendar-check" class="w-5 h-5 text-temple-red"></i>
|
||||||
<strong>{% trans "Submitted:" %}</strong> <span class="text-secondary">{{ submission.submitted_at|date:"M d, Y H:i" }}</span>
|
<span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Submitted:" %}</strong> {{ submission.submitted_at|date:"M d, Y H:i" }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="flex items-center gap-2">
|
||||||
<i class="fas fa-file-alt me-2 text-primary"></i>
|
<i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
|
||||||
<strong>{% trans "Form:" %}</strong> <span class="text-secondary">{{ submission.template.name }}</span>
|
<span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Form:" %}</strong> {{ submission.template.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if submission.applicant_name or submission.applicant_email %}
|
{% if submission.applicant_name or submission.applicant_email %}
|
||||||
<div class="row g-3 mt-1">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
{% if submission.applicant_name %}
|
{% if submission.applicant_name %}
|
||||||
<div class="col-md-4">
|
<div class="flex items-center gap-2">
|
||||||
<i class="fas fa-user me-2 text-primary"></i>
|
<i data-lucide="user" class="w-5 h-5 text-temple-red"></i>
|
||||||
<strong>{% trans "Applicant Name:" %}</strong> <span class="text-secondary">{{ submission.applicant_name }}</span>
|
<span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Applicant Name:" %}</strong> {{ submission.applicant_name }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if submission.applicant_email %}
|
{% if submission.applicant_email %}
|
||||||
<div class="col-md-4">
|
<div class="flex items-center gap-2">
|
||||||
<i class="fas fa-envelope me-2 text-primary"></i>
|
<i data-lucide="mail" class="w-5 h-5 text-temple-red"></i>
|
||||||
<strong>{% trans "Email:" %}</strong> <span class="text-secondary">{{ submission.applicant_email }}</span>
|
<span class="text-sm text-gray-600"><strong class="text-gray-900">{% trans "Email:" %}</strong> {{ submission.applicant_email }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -203,71 +69,79 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<!-- Form Responses -->
|
||||||
<div class="card-header">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<h5 class="mb-0">{% trans "Form Responses" %}</h5>
|
<div class="border-b border-gray-100 px-6 py-4">
|
||||||
|
<h5 class="text-lg font-bold text-gray-900">{% trans "Form Responses" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="p-0">
|
||||||
{% with submission=submission %}
|
{% with submission=submission %}
|
||||||
{% get_all_responses_flat submission as flat_responses %}
|
{% get_all_responses_flat submission as flat_responses %}
|
||||||
|
|
||||||
{% if flat_responses %}
|
{% if flat_responses %}
|
||||||
<div class="table-responsive">
|
<div class="overflow-x-auto">
|
||||||
<table class="table table-submission table-hover">
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
<thead>
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{% trans "Field Property" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider uppercase whitespace-nowrap sticky left-0 bg-gray-50 z-20 border-r border-gray-200 min-w-[150px]">{% trans "Field Property" %}</th>
|
||||||
{% for response in flat_responses %}
|
{% for response in flat_responses %}
|
||||||
<th scope="col">{{ response.field_label }}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider uppercase whitespace-nowrap min-w-[200px] max-w-[300px]">{{ response.field_label }}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
|
<!-- Response Value Row -->
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{% trans "Response Value" %}</strong></td>
|
<td class="px-6 py-4 font-semibold text-gray-900 sticky left-0 bg-white z-10 border-r border-gray-200 whitespace-nowrap">{% trans "Response Value" %}</td>
|
||||||
{% for response in flat_responses %}
|
{% for response in flat_responses %}
|
||||||
<td>
|
<td class="px-6 py-4 text-sm text-gray-700 max-w-[300px]">
|
||||||
{% if response.uploaded_file %}
|
{% if response.uploaded_file %}
|
||||||
<div>
|
<div class="space-y-2">
|
||||||
<span class="d-block text-truncate" style="max-width: 180px;"><i class="fas fa-file me-1"></i> {{ response.uploaded_file.name }}</span>
|
<span class="block text-sm text-gray-600 truncate max-w-[180px] inline-flex items-center gap-1">
|
||||||
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-secondary mt-1" target="_blank" title="{% trans 'Download File' %}">
|
<i data-lucide="file" class="w-4 h-4"></i> {{ response.uploaded_file.name }}
|
||||||
<i class="fas fa-download"></i> {% trans "Download" %}
|
</span>
|
||||||
|
<a href="{{ response.uploaded_file.url }}" class="inline-flex items-center gap-1 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-3 py-1.5 rounded-lg text-xs font-semibold transition" target="_blank" title="{% trans 'Download File' %}">
|
||||||
|
<i data-lucide="download" class="w-3 h-3"></i> {% trans "Download" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% elif response.value %}
|
{% elif response.value %}
|
||||||
{% if response.field_type == 'checkbox' and response.value|length > 0 %}
|
{% if response.field_type == 'checkbox' and response.value|length > 0 %}
|
||||||
<div class="d-flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
{% for val in response.value %}
|
{% for val in response.value %}
|
||||||
<span class="badge bg-secondary">{{ val }}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold uppercase bg-gray-200 text-gray-700">{{ val }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% elif response.field_type == 'radio' or response.field_type == 'select' %}
|
{% elif response.field_type == 'radio' or response.field_type == 'select' %}
|
||||||
<span class="badge bg-info">{{ response.value }}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold uppercase bg-blue-100 text-blue-800">{{ response.value }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="mb-0 small text-wrap">{{ response.value|linebreaksbr }}</p>
|
<p class="text-sm text-gray-700 line-clamp-3">{{ response.value|linebreaksbr }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted small">{% trans "Not provided" %}</span>
|
<span class="text-sm text-gray-400">{% trans "Not provided" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
<!-- Associated Stage Row -->
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{% trans "Associated Stage" %}</strong></td>
|
<td class="px-6 py-4 font-semibold text-gray-900 sticky left-0 bg-white z-10 border-r border-gray-200 whitespace-nowrap">{% trans "Associated Stage" %}</td>
|
||||||
{% for response in flat_responses %}
|
{% for response in flat_responses %}
|
||||||
<td>
|
<td class="px-6 py-4 text-sm text-gray-600">
|
||||||
<span class="small text-secondary">{{ response.stage_name|default:"N/A" }}</span>
|
{{ response.stage_name|default:"N/A" }}
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
|
<!-- Field Required Row -->
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>{% trans "Field Required" %}</strong></td>
|
<td class="px-6 py-4 font-semibold text-gray-900 sticky left-0 bg-white z-10 border-r border-gray-200 whitespace-nowrap">{% trans "Field Required" %}</td>
|
||||||
{% for response in flat_responses %}
|
{% for response in flat_responses %}
|
||||||
<td>
|
<td class="px-6 py-4">
|
||||||
{% if response.required %}
|
{% if response.required %}
|
||||||
<span class="text-danger small"><i class="fas fa-asterisk"></i> {% trans "Yes" %}</span>
|
<span class="inline-flex items-center gap-1 text-sm text-red-600 font-semibold">
|
||||||
|
<i data-lucide="asterisk" class="w-3 h-3"></i> {% trans "Yes" %}
|
||||||
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="small text-success">{% trans "No" %}</span>
|
<span class="text-sm text-emerald-600 font-semibold">{% trans "No" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -276,14 +150,18 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center text-muted py-5 px-3">
|
<div class="text-center py-12 px-6">
|
||||||
<i class="fas fa-exclamation-circle fa-2x mb-3"></i>
|
<i data-lucide="alert-circle" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
<p class="lead">{% trans "No response fields were found for this submission." %}</p>
|
<p class="text-lg font-semibold text-gray-900 mb-2">{% trans "No response fields were found for this submission." %}</p>
|
||||||
<p class="small">{% trans "This may occur if the form template was modified or responses were cleared." %}</p>
|
<p class="text-sm text-gray-500">{% trans "This may occur if the form template was modified or responses were cleared." %}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -2,306 +2,118 @@
|
|||||||
{% load static i18n form_filters %}
|
{% load static i18n form_filters %}
|
||||||
|
|
||||||
|
|
||||||
{% block title %}{% trans "All Submissions for" %} {{ template.name }} - ATS{% endblock %}
|
{% block title %}{% trans "All Submissions for" %} {{ template.name }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* ================================================= */
|
|
||||||
/* UI Variables (Matching Form Templates List) */
|
|
||||||
/* ================================================= */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-gray-light: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Typography and Color Overrides --- */
|
|
||||||
.text-primary { color: var(--kaauh-teal) !important; }
|
|
||||||
|
|
||||||
/* --- Button Base Styles (Matching Form Templates List) --- */
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary Button Style (for Edit/Preview) */
|
|
||||||
.btn-outline-secondary {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Size Utilities (matching Bootstrap convention) */
|
|
||||||
.btn-lg {
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
.btn-sm {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
padding: 0.3rem 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Card and Layout Styles (Matching Form Templates List) --- */
|
|
||||||
.card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
background-color: var(--kaauh-teal-dark) !important;
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
color: white !important;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
border-radius: 0.75rem 0.75rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header h1 {
|
|
||||||
color: white !important;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
.card-header .fas {
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
.card-header .small {
|
|
||||||
color: rgba(255, 255, 255, 0.7) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
padding: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Compact Table Styles --- */
|
|
||||||
.table-responsive {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: auto;
|
|
||||||
max-height: 70vh;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.table {
|
|
||||||
margin-bottom: 0;
|
|
||||||
min-width: max-content;
|
|
||||||
}
|
|
||||||
.table thead {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
.table thead th {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
letter-spacing: 0.3px;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.table tbody td {
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.table tbody tr {
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
.table tbody tr:hover {
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
}
|
|
||||||
/* Compact form elements */
|
|
||||||
.file-response {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
.badge-response {
|
|
||||||
margin: 0.05rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.2rem 0.4rem;
|
|
||||||
}
|
|
||||||
.response-value p {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
.btn-sm {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.2rem 0.4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Pagination Styling (Matching Form Templates List) --- */
|
|
||||||
.pagination .page-item .page-link {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
}
|
|
||||||
.pagination .page-item.active .page-link {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.pagination .page-item:hover .page-link:not(.active) {
|
|
||||||
background-color: #e9ecef;
|
|
||||||
}
|
|
||||||
.pagination-info {
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Empty State Theming --- */
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 3rem 1rem;
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
border: 2px dashed var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
}
|
|
||||||
.empty-state i {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
.empty-state .btn-main-action .fas {
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Breadcrumb --- */
|
|
||||||
.breadcrumb {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.breadcrumb-item a {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.breadcrumb-item a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.breadcrumb-item.active {
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Response Value Styling --- */
|
|
||||||
.response-value {
|
|
||||||
word-break: break-word;
|
|
||||||
max-width: 200px;
|
|
||||||
}
|
|
||||||
.file-response {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.badge-response {
|
|
||||||
margin: 0.1rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container py-4">
|
<div class="space-y-6">
|
||||||
<nav aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb">
|
<!-- Desktop Header -->
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary text-decoration-none">{% trans "Dashboard" %}</a></li>
|
<div class="hidden lg:block">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary text-decoration-none">{% trans "Form Templates" %}</a></li>
|
<!-- Breadcrumb -->
|
||||||
<li class="breadcrumb-item"><a href="{% url 'form_template_submissions_list' template.slug %}" class="text-secondary text-decoration-none">{% trans "Submissions" %}</a></li>
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
<li class="breadcrumb-item active" style="
|
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||||
color: #F43B5E;
|
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
|
||||||
font-weight: 600;
|
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Dashboard" %}
|
||||||
">{% trans "All Submissions Table" %}</li>
|
</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li><a href="{% url 'form_templates_list' %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Form Templates" %}</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li><a href="{% url 'form_template_submissions_list' template.slug %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Submissions" %}</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li class="text-temple-red font-semibold">{% trans "All Submissions Table" %}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<div class="flex justify-between items-start mb-6">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="flex-1">
|
||||||
<div>
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
<h1 class="h3 mb-1 d-flex align-items-center">
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
<i class="fas fa-table me-2"></i>
|
<i data-lucide="table" class="w-8 h-8 text-temple-red"></i>
|
||||||
{% trans "All Submissions for" %}: <span class="text-white ms-2">{{ template.name }}</span>
|
|
||||||
</h1>
|
|
||||||
<small class="text-white-50">Template ID: #{{ template.id }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-light btn-sm">
|
{% trans "All Submissions for" %}: {{ template.name }}
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
|
</h1>
|
||||||
|
<p class="text-sm text-gray-500 mt-2">Template ID: #{{ template.id }}</p>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Submissions" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Header -->
|
||||||
|
<div class="lg:hidden mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h1 class="text-xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<div class="bg-temple-red/10 p-2 rounded-lg">
|
||||||
|
<i data-lucide="table" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "All Submissions" %}
|
||||||
|
</h1>
|
||||||
|
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if page_obj.object_list %}
|
{% if page_obj.object_list %}
|
||||||
<div class="table-responsive">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<table class="table table-hover">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<thead>
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
|
<i data-lucide="list" class="w-5 h-5"></i>
|
||||||
|
{% trans "All Submissions Table" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto max-h-[70vh]">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="sticky top-0 z-10">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{% trans "Submission ID" %}</th>
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Submission ID" %}</th>
|
||||||
<th scope="col">{% trans "Applicant Name" %}</th>
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Applicant Name" %}</th>
|
||||||
<th scope="col">{% trans "Applicant Email" %}</th>
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Applicant Email" %}</th>
|
||||||
<th scope="col">{% trans "Submitted At" %}</th>
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Submitted At" %}</th>
|
||||||
{% for field in fields %}
|
{% for field in fields %}
|
||||||
<th scope="col">{{ field.label }}</th>
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{{ field.label }}</th>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="bg-white divide-y divide-gray-100 text-sm">
|
||||||
{% for submission in page_obj %}
|
{% for submission in page_obj %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
<td class="fw-medium">{{ submission.id }}</td>
|
<td class="px-4 py-2 font-semibold text-gray-900 whitespace-nowrap">{{ submission.id }}</td>
|
||||||
<td>{{ submission.applicant_name|default:"N/A" }}</td>
|
<td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.applicant_name|default:"N/A" }}</td>
|
||||||
<td>{{ submission.applicant_email|default:"N/A" }}</td>
|
<td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.applicant_email|default:"N/A" }}</td>
|
||||||
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
<td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
||||||
{% for field in fields %}
|
{% for field in fields %}
|
||||||
{% get_field_response_for_submission submission field as response %}
|
{% get_field_response_for_submission submission field as response %}
|
||||||
<td class="response-value">
|
<td class="px-4 py-2 max-w-[200px]">
|
||||||
{% if response %}
|
{% if response %}
|
||||||
{% if response.uploaded_file %}
|
{% if response.uploaded_file %}
|
||||||
<div class="file-response">
|
<div class="flex items-center gap-2">
|
||||||
|
<a href="{{ response.uploaded_file.url }}"
|
||||||
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-primary" target="_blank" title="Download File">
|
class="inline-flex items-center gap-1 bg-temple-red/10 hover:bg-temple-red/20 text-temple-red px-2 py-1 rounded text-xs transition"
|
||||||
<i class="fas fa-download"></i>
|
target="_blank"
|
||||||
|
title="{% trans 'Download File' %}">
|
||||||
|
<i data-lucide="download" class="w-3 h-3"></i> {% trans "Download" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% elif response.value %}
|
{% elif response.value %}
|
||||||
{% if response.field.field_type == 'checkbox' and response.value|length > 0 %}
|
{% if response.field.field_type == 'checkbox' and response.value|length > 0 %}
|
||||||
<div>
|
<div class="flex flex-wrap gap-1">
|
||||||
{% for val in response.value|to_list %}
|
{% for val in response.value|to_list %}
|
||||||
<span class="badge bg-secondary badge-response">{{ val }}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-gray-200 text-gray-700">{{ val }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% elif response.field.field_type == 'radio' or response.field.field_type == 'select' %}
|
{% elif response.field.field_type == 'radio' or response.field.field_type == 'select' %}
|
||||||
<span class="badge bg-info">{{ response.value }}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-blue-100 text-blue-800">{{ response.value }}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="mb-0">{{ response.value|linebreaksbr|truncatewords:10 }}</p>
|
<p class="text-xs text-gray-700 line-clamp-2">{{ response.value|linebreaksbr|truncatewords:10 }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">Not provided</span>
|
<span class="text-xs text-gray-400">Not provided</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">Not provided</span>
|
<span class="text-xs text-gray-400">Not provided</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -310,65 +122,57 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
{% if page_obj.has_other_pages %}
|
{% if page_obj.has_other_pages %}
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center mt-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||||
<div class="pagination-info mb-3 mb-md-0">
|
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
||||||
Showing {{ start }} to {{ end }} of {{ total }} results.
|
Showing {{ start }} to {{ end }} of {{ total }} results.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
<nav aria-label="Page navigation">
|
<nav aria-label="Page navigation" class="flex items-center gap-2">
|
||||||
<ul class="pagination pagination-sm mb-0">
|
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<li class="page-item">
|
<a href="?page=1" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="First">
|
||||||
<a class="page-link" href="?page=1" aria-label="First">
|
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||||
<span aria-hidden="true">«</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a href="?page={{ page_obj.previous_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Previous">
|
||||||
<li class="page-item">
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
|
|
||||||
<span aria-hidden="true">‹</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<li class="page-item active">
|
<span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||||
<span class="page-link">
|
|
||||||
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<li class="page-item">
|
<a href="?page={{ page_obj.next_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Next">
|
||||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||||
<span aria-hidden="true">›</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a href="?page={{ page_obj.paginator.num_pages }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Last">
|
||||||
<li class="page-item">
|
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
|
|
||||||
<span aria-hidden="true">»</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="empty-state">
|
<!-- No Results -->
|
||||||
<i class="fas fa-inbox"></i>
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||||
<h3 class="h5 mb-3">{% trans "No Submissions Found" %}</h3>
|
<i data-lucide="inbox" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
<p class="text-muted mb-4">
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No Submissions Found" %}</h3>
|
||||||
{% trans "There are no submissions for this form template yet." %}
|
<p class="text-gray-500 mb-6">{% trans "There are no submissions for this form template yet." %}</p>
|
||||||
</p>
|
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-main-action">
|
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Submissions" %}
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,279 +1,98 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load static i18n crispy_forms_tags %}
|
{% load static i18n %}
|
||||||
|
|
||||||
|
|
||||||
{% block title %}{% trans "Submissions for" %} {{ template.name }} - ATS{% endblock %}
|
{% block title %}{% trans "Submissions for" %} {{ template.name }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* ================================================= */
|
|
||||||
/* UI Variables (Matching Form Templates List) */
|
|
||||||
/* ================================================= */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-gray-light: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Typography and Color Overrides --- */
|
|
||||||
.text-primary { color: var(--kaauh-teal) !important; }
|
|
||||||
|
|
||||||
/* --- Button Base Styles (Matching Form Templates List) --- */
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 900 ;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary Button Style (for Edit/Preview) */
|
|
||||||
.btn-outline-secondary {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Size Utilities (matching Bootstrap convention) */
|
|
||||||
.btn-lg {
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
.btn-sm {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
padding: 0.3rem 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Card and Layout Styles (Matching Form Templates List) --- */
|
|
||||||
.card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
background-color: var(--kaauh-teal-dark) !important;
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
color: white !important;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 1rem 1.25rem;
|
|
||||||
border-radius: 0.75rem 0.75rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header h1 {
|
|
||||||
color: white !important;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
.card-header .fas {
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
.card-header .small {
|
|
||||||
color: rgba(255, 255, 255, 0.7) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-body {
|
|
||||||
padding: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Table Styles --- */
|
|
||||||
.table-responsive {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.table {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
.table thead th {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
.table tbody td {
|
|
||||||
padding: 1rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
}
|
|
||||||
.table tbody tr {
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
.table tbody tr:hover {
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Pagination Styling (Matching Form Templates List) --- */
|
|
||||||
.pagination .page-item .page-link {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
}
|
|
||||||
.pagination .page-item.active .page-link {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.pagination .page-item:hover .page-link:not(.active) {
|
|
||||||
background-color: #e9ecef;
|
|
||||||
}
|
|
||||||
.pagination-info {
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Empty State Theming --- */
|
|
||||||
.empty-state {
|
|
||||||
text-align: center;
|
|
||||||
padding: 3rem 1rem;
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
border: 2px dashed var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
}
|
|
||||||
.empty-state i {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
.empty-state .btn-main-action .fas {
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Breadcrumb --- */
|
|
||||||
.breadcrumb {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.breadcrumb-item a {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.breadcrumb-item a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.breadcrumb-item.active {
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% comment %} <div class="container py-4">
|
<div class="space-y-6">
|
||||||
<!-- Search and Filter Section -->
|
|
||||||
<div class="card shadow-sm mb-4">
|
<!-- Desktop Header -->
|
||||||
<div class="card-body">
|
<div class="hidden lg:block">
|
||||||
<form method="get" action="" class="w-100">
|
<!-- Breadcrumb -->
|
||||||
<div class="row g-3 align-items-end">
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
<div class="col-12 col-md-5">
|
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||||
<label for="search" class="form-label small text-muted">{% trans "Search by name or Email" %}</label>
|
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
|
||||||
<div class="input-group">
|
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Dashboard" %}
|
||||||
{% include 'includes/search_form.html' %}
|
</a></li>
|
||||||
</div>
|
<li class="text-gray-400">/</li>
|
||||||
</div>
|
<li><a href="{% url 'form_templates_list' %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Form Templates" %}</a></li>
|
||||||
<div class="col-12 col-md-2">
|
<li class="text-gray-400">/</li>
|
||||||
<label for="date_from" class="form-label small text-muted">{% trans "From Date" %}</label>
|
<li class="text-temple-red font-semibold">{% trans "Submissions" %}</li>
|
||||||
<input type="date" class="form-control" id="date_from" name="date_from" value="{{ request.GET.date_from }}">
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-2">
|
|
||||||
<label for="date_to" class="form-label small text-muted">{% trans "To Date" %}</label>
|
|
||||||
<input type="date" class="form-control" id="date_to" name="date_to" value="{{ request.GET.date_to }}">
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-3 d-flex gap-2">
|
|
||||||
<button type="submit" class="btn btn-main-action flex-grow-1">
|
|
||||||
<i class="fas fa-search me-1"></i> {% trans "Filter" %}
|
|
||||||
</button>
|
|
||||||
<a href="?" class="btn btn-outline-secondary flex-grow-1">
|
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endcomment %}
|
|
||||||
<div class="container py-4">
|
|
||||||
<nav aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">{% trans "Dashboard" %}</a></li>
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary">{% trans "Form Templates" %}</a></li>
|
|
||||||
<li class="breadcrumb-item active" style="
|
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;
|
|
||||||
">{% trans "Submissions" %}</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<div class="flex justify-between items-start mb-6">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="flex-1">
|
||||||
<div>
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
<h1 class="h3 mb-1 d-flex align-items-center">
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
<i class="fas fa-file-alt me-2"></i>
|
<i data-lucide="file-text" class="w-8 h-8 text-temple-red"></i>
|
||||||
{% trans "Submissions for" %}: <span class="text-white ms-2">{{ template.name }}</span>
|
</div>
|
||||||
|
{% trans "Submissions for" %}: {{ template.name }}
|
||||||
</h1>
|
</h1>
|
||||||
<small class="text-white-50">Template ID: #{{ template.id }}</small>
|
<p class="text-sm text-gray-500 mt-2">Template ID: #{{ template.id }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="flex gap-2">
|
||||||
<a href="{% url 'form_template_all_submissions' template.id %}" class="btn btn-outline-light btn-sm">
|
<a href="{% url 'form_template_all_submissions' template.id %}" class="inline-flex items-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-4 py-2.5 rounded-xl text-sm transition font-medium">
|
||||||
<i class="fas fa-table me-1"></i> {% trans "View All in Table" %}
|
<i data-lucide="table" class="w-4 h-4"></i> {% trans "View All in Table" %}
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'form_templates_list' %}" class="btn btn-outline-light btn-sm">
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Templates" %}
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Templates" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Header -->
|
||||||
|
<div class="lg:hidden mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h1 class="text-xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<div class="bg-temple-red/10 p-2 rounded-lg">
|
||||||
|
<i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Submissions" %}
|
||||||
|
</h1>
|
||||||
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if page_obj.object_list %}
|
{% if page_obj.object_list %}
|
||||||
<div id="form-template-submissions-list">
|
<div id="form-template-submissions-list">
|
||||||
{# View Switcher #}
|
{# View Switcher #}
|
||||||
{% include "includes/_list_view_switcher.html" with list_id="form-template-submissions-list" %}
|
{% include "includes/_list_view_switcher.html" with list_id="form-template-submissions-list" %}
|
||||||
|
|
||||||
{# Table View (Default) #}
|
{# Table View (Default) #}
|
||||||
<div class="table-view active">
|
<div class="table-view active hidden lg:block bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="table-responsive mb-4">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<table class="table table-hover">
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
<thead>
|
<i data-lucide="list" class="w-5 h-5"></i>
|
||||||
|
{% trans "Submission Listings" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{% trans "Submission ID" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Submission ID" %}</th>
|
||||||
<th scope="col">{% trans "Applicant Name" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Applicant Name" %}</th>
|
||||||
<th scope="col">{% trans "Applicant Email" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Applicant Email" %}</th>
|
||||||
<th scope="col">{% trans "Submitted At" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Submitted At" %}</th>
|
||||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
<th scope="col" class="px-6 py-3 text-right text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="bg-white divide-y divide-gray-100">
|
||||||
{% for submission in page_obj %}
|
{% for submission in page_obj %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
<td class="fw-medium">{{ submission.id }}</td>
|
<td class="px-6 py-4 font-semibold text-gray-900">{{ submission.id }}</td>
|
||||||
<td>{{ submission.applicant_name|default:"N/A" }}</td>
|
<td class="px-6 py-4 text-sm text-gray-700">{{ submission.applicant_name|default:"N/A" }}</td>
|
||||||
<td>{{ submission.applicant_email|default:"N/A" }}</td>
|
<td class="px-6 py-4 text-sm text-gray-700">{{ submission.applicant_email|default:"N/A" }}</td>
|
||||||
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
<td class="px-6 py-4 text-sm text-gray-700">{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
||||||
<td class="text-end">
|
<td class="px-6 py-4 text-right">
|
||||||
<a href="{% url 'form_submission_details' template_id=submission.template.id slug=submission.slug %}" class="btn btn-sm btn-outline-primary">
|
<a href="{% url 'form_submission_details' template_id=submission.template.id slug=submission.slug %}"
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2 rounded-lg text-sm transition">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Details" %}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -284,92 +103,80 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Card View #}
|
{# Card View #}
|
||||||
<div class="card-view">
|
<div class="card-view lg:hidden grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="row g-4">
|
|
||||||
{% for submission in page_obj %}
|
{% for submission in page_obj %}
|
||||||
<div class="col-md-6 col-lg-6">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="card h-100">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<div class="card-header">
|
<h3 class="h5 font-bold mb-1">{% trans "Submission" %} #{{ submission.id }}</h3>
|
||||||
<h3 class="h5 mb-2">{% trans "Submission" %} #{{ submission.id }}</h3>
|
<small class="text-white/70">{{ template.name }}</small>
|
||||||
<small class="text-white-50">{{ template.name }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-4 space-y-2">
|
||||||
<p class="card-text">
|
<p class="text-sm text-gray-700">
|
||||||
<strong>{% trans "Applicant Name" %}:</strong> {{ submission.applicant_name|default:"N/A" }}<br>
|
<span class="font-semibold">{% trans "Applicant Name" %}:</span> {{ submission.applicant_name|default:"N/A" }}<br>
|
||||||
<strong>{% trans "Applicant Email" %}:</strong> {{ submission.applicant_email|default:"N/A" }}<br>
|
<span class="font-semibold">{% trans "Applicant Email" %}:</span> {{ submission.applicant_email|default:"N/A" }}<br>
|
||||||
<strong>{% trans "Submitted At" %}:</strong> {{ submission.submitted_at|date:"M d, Y H:i" }}
|
<span class="font-semibold">{% trans "Submitted At" %}:</span> {{ submission.submitted_at|date:"M d, Y H:i" }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="p-4 pt-0">
|
||||||
<a href="{% url 'form_submission_details' template_id=template.id slug=submission.slug %}" class="btn btn-sm btn-outline-primary w-100">
|
<a href="{% url 'form_submission_details' template_id=template.id slug=submission.slug %}"
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
class="w-full inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2.5 rounded-xl text-sm transition font-medium">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Details" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
{% if page_obj.has_other_pages %}
|
{% if page_obj.has_other_pages %}
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||||
<div class="pagination-info mb-3 mb-md-0">
|
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
||||||
Showing {{ start }} to {{ end }} of {{ total }} results.
|
Showing {{ start }} to {{ end }} of {{ total }} results.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
<nav aria-label="Page navigation">
|
<nav aria-label="Page navigation" class="flex items-center gap-2">
|
||||||
<ul class="pagination pagination-sm mb-0">
|
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<li class="page-item">
|
<a href="?page=1" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="First">
|
||||||
<a class="page-link" href="?page=1" aria-label="First">
|
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||||
<span aria-hidden="true">«</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a href="?page={{ page_obj.previous_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Previous">
|
||||||
<li class="page-item">
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
|
|
||||||
<span aria-hidden="true">‹</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<li class="page-item active">
|
<span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||||
<span class="page-link">
|
|
||||||
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<li class="page-item">
|
<a href="?page={{ page_obj.next_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Next">
|
||||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||||
<span aria-hidden="true">›</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a href="?page={{ page_obj.paginator.num_pages }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Last">
|
||||||
<li class="page-item">
|
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
|
|
||||||
<span aria-hidden="true">»</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="empty-state">
|
<!-- No Results -->
|
||||||
<i class="fas fa-inbox"></i>
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||||
<h3 class="h5 mb-3">{% trans "No Submissions Found" %}</h3>
|
<i data-lucide="inbox" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
<p class="text-muted mb-4">
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No Submissions Found" %}</h3>
|
||||||
{% trans "There are no submissions for this form template yet." %}
|
<p class="text-gray-500 mb-6">{% trans "There are no submissions for this form template yet." %}</p>
|
||||||
</p>
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<a href="{% url 'form_templates_list' %}" class="btn btn-main-action">
|
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Templates" %}
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Templates" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -4,191 +4,109 @@
|
|||||||
|
|
||||||
{% block title %}{% trans "Form Templates" %} - {{ block.super }}{% endblock %}
|
{% block title %}{% trans "Form Templates" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* ================================================= */
|
|
||||||
/* UI Variables (Matching Standard Theme) */
|
|
||||||
/* ================================================= */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-gray-light: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Typography and Color Overrides --- */
|
|
||||||
.text-primary { color: var(--kaauh-teal) !important; }
|
|
||||||
|
|
||||||
/* --- Button Base Styles (Consistent) --- */
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.4rem;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
transform: none; /* Removed translate to match other lists */
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary Button Style (for Edit/Preview) */
|
|
||||||
.btn-outline-secondary {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
/* Primary Outline for View/Preview */
|
|
||||||
.btn-outline-primary {
|
|
||||||
color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.btn-outline-primary:hover {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Card and Layout Styles (Consistent) --- */
|
|
||||||
.card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
|
||||||
}
|
|
||||||
.card:not(.no-hover):hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 6px 16px rgba(0,0,0,0.1) !important;
|
|
||||||
}
|
|
||||||
.card.no-hover:hover {
|
|
||||||
transform: none;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card Header (For Search/Filter Card) */
|
|
||||||
.card-header {
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 1.25rem;
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stats Theming */
|
|
||||||
.stat-value {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 800;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
.stat-label {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table Styling (Consistent) */
|
|
||||||
.table-view .table thead th {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
.table-view .table tbody td {
|
|
||||||
vertical-align: middle;
|
|
||||||
padding: 1rem;
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
}
|
|
||||||
.table-view .table tbody tr:hover {
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pagination Styling (Consistent) */
|
|
||||||
.pagination .page-item .page-link {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-border);
|
|
||||||
}
|
|
||||||
.pagination .page-item.active .page-link {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.pagination .page-item:hover .page-link:not(.active) {
|
|
||||||
background-color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Empty State Icon Color */
|
|
||||||
.empty-state i, .text-center i.fa-3x {
|
|
||||||
color: var(--kaauh-teal-dark) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Filter Buttons Container */
|
|
||||||
.filter-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="space-y-6">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<!-- Desktop Header -->
|
||||||
<i class="fas fa-file-alt me-2"></i>{% trans "Form Templates" %}
|
<div class="hidden lg:block">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
|
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||||
|
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
|
||||||
|
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Home" %}
|
||||||
|
</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li class="text-temple-red font-semibold">{% trans "Form Templates" %}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="file-text" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Form Templates" %}
|
||||||
</h1>
|
</h1>
|
||||||
<button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#createTemplateModal">
|
<button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md"
|
||||||
<i class="fas fa-plus me-1"></i> {% trans "Create New Template" %}
|
onclick="document.getElementById('createTemplateModal').classList.remove('hidden'); document.getElementById('createTemplateModal').classList.add('flex');">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create New Template" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Search/Filter Area - Matching Standard Structure #}
|
<!-- Desktop Filters -->
|
||||||
<div class="card mb-4 shadow-sm no-hover">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden mb-6">
|
||||||
<div class="card-body">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<form method="get" class="row g-3 align-items-end">
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
|
<i data-lucide="filter" class="w-5 h-5"></i>
|
||||||
<div class="col-md-6">
|
{% trans "Filter Templates" %}
|
||||||
<label for="searchInput" class="form-label small text-muted">{% trans "Search by Template Name" %}</label>
|
</h5>
|
||||||
<div class="input-group input-group-lg">
|
</div>
|
||||||
<span class="input-group-text"><i class="fas fa-search text-muted"></i></span>
|
<div class="p-6">
|
||||||
<input type="text" name="q" id="searchInput" class="form-control form-control-search"
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label for="searchInput" class="block text-xs font-semibold text-gray-600 mb-2">{% trans "Search by Template Name" %}</label>
|
||||||
|
<form method="get" class="flex gap-3">
|
||||||
|
<div class="relative flex-1">
|
||||||
|
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i>
|
||||||
|
<input type="text" name="q" id="searchInput"
|
||||||
|
class="w-full pl-10 pr-4 py-3 bg-white border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
|
||||||
placeholder="{% trans 'Search templates by name...' %}"
|
placeholder="{% trans 'Search templates by name...' %}"
|
||||||
value="{{ query|default_if_none:'' }}">
|
value="{{ query|default_if_none:'' }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-5 py-2.5 rounded-xl text-sm transition whitespace-nowrap">
|
||||||
|
<i data-lucide="filter" class="w-4 h-4"></i> {% trans "Search" %}
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="filter-buttons">
|
|
||||||
<button type="submit" class="btn btn-main-action btn-lg">
|
|
||||||
<i class="fas fa-filter me-1"></i> {% trans "Search" %}
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{# Show Clear button if search is active #}
|
|
||||||
{% if query %}
|
{% if query %}
|
||||||
<a href="{% url 'form_templates_list' %}" class="btn btn-outline-secondary btn-lg">
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition whitespace-nowrap">
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Clear Search" %}
|
<i data-lucide="x" class="w-4 h-4"></i> {% trans "Clear" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Header -->
|
||||||
|
<div class="lg:hidden mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<div class="bg-temple-red/10 p-2 rounded-lg">
|
||||||
|
<i data-lucide="file-text" class="w-6 h-6 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Form Templates" %}
|
||||||
|
</h1>
|
||||||
|
<button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl text-sm transition shadow-sm"
|
||||||
|
onclick="document.getElementById('createTemplateModal').classList.remove('hidden'); document.getElementById('createTemplateModal').classList.add('flex');">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Filters -->
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div>
|
||||||
|
<label for="searchInputMobile" class="block text-xs font-semibold text-gray-600 mb-2">{% trans "Search Templates" %}</label>
|
||||||
|
<form method="get" class="flex gap-2">
|
||||||
|
<div class="relative flex-1">
|
||||||
|
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i>
|
||||||
|
<input type="text" name="q" id="searchInputMobile"
|
||||||
|
class="w-full pl-10 pr-4 py-3 bg-white border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
|
||||||
|
placeholder="{% trans 'Search...' %}"
|
||||||
|
value="{{ query|default_if_none:'' }}">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-3 rounded-xl transition">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
{% if query %}
|
||||||
|
<a href="{% url 'form_templates_list' %}" class="bg-gray-100 hover:bg-gray-200 text-gray-600 px-4 py-3 rounded-xl transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if templates %}
|
{% if templates %}
|
||||||
<div id="form-templates-list">
|
<div id="form-templates-list">
|
||||||
@ -196,110 +114,108 @@
|
|||||||
{% include "includes/_list_view_switcher.html" with list_id="form-templates-list" %}
|
{% include "includes/_list_view_switcher.html" with list_id="form-templates-list" %}
|
||||||
|
|
||||||
{# Card View (Default) #}
|
{# Card View (Default) #}
|
||||||
<div class="card-view active row g-4">
|
<div class="card-view active lg:hidden grid grid-cols-1 gap-4">
|
||||||
{% for template in templates %}
|
{% for template in templates %}
|
||||||
<div class="col-lg-4 col-md-6">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="card template-card h-100 shadow-sm">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<div class="card-body d-flex flex-column">
|
<h5 class="font-bold text-lg">{{ template.name }}</h5>
|
||||||
<h5 class="card-title fw-bold" style="color: var(--kaauh-teal-dark);">{{ template.name }}</h5>
|
<span class="text-sm text-white/80">
|
||||||
<span class="text-muted small mb-3">
|
<i data-lucide="briefcase" class="w-4 h-4 inline mr-1"></i> {{ template.job|default:"N/A" }}
|
||||||
<i class="fas fa-briefcase me-1"></i> {{ template.job|default:"N/A" }}
|
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{# Stats #}
|
|
||||||
<div class="row text-center mb-3">
|
|
||||||
<div class="col-6 border-end">
|
|
||||||
<div class="stat-value">{{ template.get_stage_count }}</div>
|
|
||||||
<div class="stat-label">{% trans "Stages" %}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="p-4 space-y-3">
|
||||||
<div class="stat-value">{{ template.get_field_count }}</div>
|
{# Stats #}
|
||||||
<div class="stat-label">{% trans "Fields" %}</div>
|
<div class="grid grid-cols-2 gap-2 text-center mb-3">
|
||||||
|
<div class="p-3 bg-gray-50 rounded-xl">
|
||||||
|
<div class="text-2xl font-bold text-temple-red">{{ template.get_stage_count }}</div>
|
||||||
|
<div class="text-[10px] uppercase text-gray-500 font-semibold">{% trans "Stages" %}</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 bg-gray-50 rounded-xl">
|
||||||
|
<div class="text-2xl font-bold text-temple-red">{{ template.get_field_count }}</div>
|
||||||
|
<div class="text-[10px] uppercase text-gray-500 font-semibold">{% trans "Fields" %}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Description #}
|
{# Description #}
|
||||||
<p class="card-text small text-muted flex-grow-1">
|
<p class="text-sm text-gray-600">
|
||||||
{% if template.description %}
|
{% if template.description %}
|
||||||
{{ template.description|truncatewords:20 }}
|
{{ template.description|truncatewords:20 }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<em class="text-muted">{% trans "No description provided" %}</em>
|
<em class="text-gray-400">{% trans "No description provided" %}</em>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{# Action area #}
|
{# Actions #}
|
||||||
<div class="mt-auto pt-2 border-top">
|
<div class="flex gap-2 pt-3 border-t border-gray-100">
|
||||||
<div class="d-flex gap-2 justify-content-end">
|
<a href="{% url 'application_submit_form' template.job.slug %}" class="inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition flex-1">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "Preview" %}
|
||||||
<a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'Preview' %}">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}">
|
<a href="{% url 'form_builder' template.slug %}" class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-gray-300 hover:bg-gray-100 text-gray-600 transition">
|
||||||
<i class="fas fa-edit"></i>
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Submissions' %}">
|
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-gray-300 hover:bg-gray-100 text-gray-600 transition">
|
||||||
<i class="fas fa-file-alt"></i>
|
<i data-lucide="file-text" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}"
|
<button type="button" class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-red-300 bg-red-50 hover:bg-red-100 text-red-600 transition"
|
||||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
onclick="deleteTemplate('{{ template.name }}', '#')">
|
||||||
data-delete-url="#"
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
data-item-name="{{ template.name }}">
|
|
||||||
<i class="fas fa-trash-alt"></i>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="bg-gray-50 text-gray-500 text-xs p-3 flex justify-between">
|
||||||
<div class="card-footer bg-light text-muted small">
|
<span><i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i> {% trans "Created:" %} {{ template.created_at|date:"M d, Y" }}</span>
|
||||||
<div class="d-flex justify-content-between">
|
<span><i data-lucide="refresh-cw" class="w-3 h-3 inline mr-1"></i> {{ template.updated_at|timesince }} {% trans "ago" %}</span>
|
||||||
<span><i class="fas fa-calendar-alt me-1"></i> {% trans "Created:" %} {{ template.created_at|date:"M d, Y" }}</span>
|
|
||||||
<span><i class="fas fa-sync-alt me-1"></i> {{ template.updated_at|timesince }} {% trans "ago" %}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Table View #}
|
{# Table View #}
|
||||||
<div class="table-view">
|
<div class="hidden lg:block bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="table-responsive">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<table class="table table-hover align-middle mb-0">
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
<thead>
|
<i data-lucide="list" class="w-5 h-5"></i>
|
||||||
|
{% trans "Template Listings" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" style="width: 30%;">{% trans "Template Name" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Template Name" %}</th>
|
||||||
<th scope="col" style="width: 15%;">{% trans "Job" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Job" %}</th>
|
||||||
<th scope="col" style="width: 8%;">{% trans "Stages" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Stages" %}</th>
|
||||||
<th scope="col" style="width: 8%;">{% trans "Fields" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Fields" %}</th>
|
||||||
<th scope="col" style="width: 15%;">{% trans "Created" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Created" %}</th>
|
||||||
<th scope="col" style="width: 15%;">{% trans "Last Updated" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Last Updated" %}</th>
|
||||||
<th scope="col" style="width: 9%;" class="text-end">{% trans "Actions" %}</th>
|
<th scope="col" class="px-6 py-3 text-center text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="bg-white divide-y divide-gray-100">
|
||||||
{% for template in templates %}
|
{% for template in templates %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
<td class="fw-medium text-primary">{{ template.name }}</td>
|
<td class="px-6 py-4">
|
||||||
<td>{{ template.job|default:"N/A" }}</td>
|
<a href="#" class="text-temple-red font-semibold hover:text-[#7a1a29] transition-colors">{{ template.name }}</a>
|
||||||
<td>{{ template.get_stage_count }}</td>
|
</td>
|
||||||
<td>{{ template.get_field_count }}</td>
|
<td class="px-6 py-4 text-sm text-gray-700">{{ template.job|default:"N/A" }}</td>
|
||||||
<td>{{ template.created_at|date:"M d, Y" }}</td>
|
<td class="px-6 py-4 text-sm text-gray-700">{{ template.get_stage_count }}</td>
|
||||||
<td>{{ template.updated_at|date:"M d, Y" }}</td>
|
<td class="px-6 py-4 text-sm text-gray-700">{{ template.get_field_count }}</td>
|
||||||
<td class="text-end">
|
<td class="px-6 py-4 text-sm text-gray-700">{{ template.created_at|date:"M d, Y" }}</td>
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<td class="px-6 py-4 text-sm text-gray-700">{{ template.updated_at|date:"M d, Y" }}</td>
|
||||||
<a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary" title="{% trans 'Preview' %}">
|
<td class="px-6 py-4 text-center">
|
||||||
<i class="fas fa-eye"></i>
|
<div class="flex items-center justify-center gap-2">
|
||||||
|
<a href="{% url 'application_submit_form' template.job.slug %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-temple-red/10 hover:bg-temple-red/20 text-temple-red transition" title="{% trans 'Preview' %}">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
<a href="{% url 'form_builder' template.slug %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-600 transition" title="{% trans 'Edit' %}">
|
||||||
<i class="fas fa-edit"></i>
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Submissions' %}">
|
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-600 transition" title="{% trans 'Submissions' %}">
|
||||||
<i class="fas fa-file-alt"></i>
|
<i data-lucide="file-text" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
<button type="button" class="btn btn-outline-danger" title="{% trans 'Delete' %}"
|
<button type="button" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-red-300 bg-red-50 hover:bg-red-100 text-red-600 transition" title="{% trans 'Delete' %}"
|
||||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
onclick="deleteTemplate('{{ template.name }}', '#')">
|
||||||
data-delete-url="#"
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
data-item-name="{{ template.name }}">
|
|
||||||
<i class="fas fa-trash-alt"></i>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@ -311,80 +227,98 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Pagination (Standardized) #}
|
{# Pagination #}
|
||||||
{% if templates.has_other_pages %}
|
{% if templates.has_other_pages %}
|
||||||
<nav aria-label="Page navigation" class="mt-4">
|
<nav aria-label="Page navigation" class="flex justify-center items-center gap-2">
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
{% if templates.has_previous %}
|
{% if templates.has_previous %}
|
||||||
<li class="page-item">
|
<a href="?page=1{% if query %}&q={{ query }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition">
|
||||||
<a class="page-link" href="?page=1{% if query %}&q={{ query }}{% endif %}">First</a>
|
{% trans "First" %}
|
||||||
</li>
|
</a>
|
||||||
<li class="page-item">
|
<a href="?page={{ templates.previous_page_number }}{% if query %}&q={{ query }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition">
|
||||||
<a class="page-link" href="?page={{ templates.previous_page_number }}{% if query %}&q={{ query }}{% endif %}">Previous</a>
|
{% trans "Previous" %}
|
||||||
</li>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<li class="page-item active">
|
<span class="px-3 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||||
<span class="page-link">{{ templates.number }} of {{ templates.paginator.num_pages }}</span>
|
{{ templates.number }} {% trans "of" %} {{ templates.paginator.num_pages }}
|
||||||
</li>
|
</span>
|
||||||
|
|
||||||
{% if templates.has_next %}
|
{% if templates.has_next %}
|
||||||
<li class="page-item">
|
<a href="?page={{ templates.next_page_number }}{% if query %}&q={{ query }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition">
|
||||||
<a class="page-link" href="?page={{ templates.next_page_number }}{% if query %}&q={{ query }}{% endif %}">Next</a>
|
{% trans "Next" %}
|
||||||
</li>
|
</a>
|
||||||
<li class="page-item">
|
<a href="?page={{ templates.paginator.num_pages }}{% if query %}&q={{ query }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition">
|
||||||
<a class="page-link" href="?page={{ templates.paginator.num_pages }}{% if query %}&q={{ query }}{% endif %}">Last</a>
|
{% trans "Last" %}
|
||||||
</li>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-5 card shadow-sm">
|
<!-- No Results -->
|
||||||
<div class="card-body">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||||
<i class="fas fa-file-contract fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
|
<i data-lucide="file-text" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
<h3 class="h4 mb-3">{% trans "No Form Templates Found" %}</h3>
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No Form Templates Found" %}</h3>
|
||||||
<p class="text-muted">
|
<p class="text-gray-500 mb-6">
|
||||||
{% if query %}
|
{% if query %}
|
||||||
{% blocktrans with query=query %}No templates match your search "{{ query }}".{% endblocktrans %}
|
{% blocktrans with query=query %}No templates match your search "{{ query }}".{% endblocktrans %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "You haven't created any form templates yet." %}
|
{% trans "You haven't created any form templates yet." %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<button type="button" class="btn btn-main-action mt-3" data-bs-toggle="modal" data-bs-target="#createTemplateModal">
|
<button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md"
|
||||||
<i class="fas fa-plus me-1"></i> {% trans "Create Your First Template" %}
|
onclick="document.getElementById('createTemplateModal').classList.remove('hidden'); document.getElementById('createTemplateModal').classList.add('flex');">
|
||||||
|
<i data-lucide="plus" class="w-5 h-5"></i> {% trans "Create Your First Template" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include 'includes/delete_modal.html' %}
|
{% include 'includes/delete_modal.html' %}
|
||||||
|
|
||||||
<div class="modal fade" id="createTemplateModal" tabindex="-1" aria-labelledby="createTemplateModalLabel" aria-hidden="true">
|
<!-- Create Template Modal -->
|
||||||
<div class="modal-dialog modal-lg">
|
<div id="createTemplateModal" class="fixed inset-0 z-50 hidden">
|
||||||
<div class="modal-content">
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeCreateModal()"></div>
|
||||||
<div class="modal-header bg-light">
|
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||||
<h5 class="modal-title" id="createTemplateModalLabel">
|
<div class="relative bg-white rounded-2xl shadow-2xl w-full max-w-lg">
|
||||||
<i class="fas fa-file-alt me-2"></i>{% trans "Create New Form Template" %}
|
<div class="bg-gray-50 px-6 py-4 border-b border-gray-200 rounded-t-2xl">
|
||||||
</h5>
|
<h3 class="text-lg font-bold text-gray-900 flex items-center gap-2">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
{% trans "Create New Form Template" %}
|
||||||
|
</h3>
|
||||||
|
<button type="button" onclick="closeCreateModal()" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition">
|
||||||
|
<i data-lucide="x" class="w-6 h-6"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="p-6">
|
||||||
{% url 'create_form_template' as create_form_template_url %}
|
|
||||||
<form id="createTemplateForm" method="post" action="{% url 'create_form_template' %}">
|
<form id="createTemplateForm" method="post" action="{% url 'create_form_template' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{form|crispy}}
|
{{form|crispy}}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="px-6 py-4 border-t border-gray-200 flex gap-3 justify-end">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
<button type="button" onclick="closeCreateModal()" class="px-4 py-2 border border-gray-300 text-gray-700 rounded-xl hover:bg-gray-50 transition text-sm">
|
||||||
<button type="submit" form="createTemplateForm" class="btn btn-main-action">
|
{% trans "Cancel" %}
|
||||||
<i class="fas fa-save me-1"></i>{% trans "Create Template" %}
|
</button>
|
||||||
|
<button type="submit" form="createTemplateForm" class="px-4 py-2 bg-temple-red hover:bg-[#7a1a29] text-white rounded-xl transition text-sm font-semibold flex items-center gap-2">
|
||||||
|
<i data-lucide="save" class="w-4 h-4"></i> {% trans "Create Template" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
|
function closeCreateModal() {
|
||||||
|
document.getElementById('createTemplateModal').classList.add('hidden');
|
||||||
|
document.getElementById('createTemplateModal').classList.remove('flex');
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteTemplate(name, url) {
|
||||||
|
if (confirm('{% trans "Are you sure you want to delete this template?" %}')) {
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,52 +1,52 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="flex justify-between items-center mb-3">
|
||||||
<div class="btn-group" role="group">
|
<div class="inline-flex rounded-lg overflow-hidden" role="group">
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm view-toggle active" data-view="table" data-list-id="{{ list_id }}">
|
<button type="button" class="view-toggle active inline-flex items-center gap-2 px-4 py-2 text-sm font-medium border border-r-0 transition" data-view="table" data-list-id="{{ list_id }}">
|
||||||
<i class="fas fa-table me-1"></i> {% trans "Table" %}
|
<i data-lucide="table" class="w-4 h-4"></i> {% trans "Table" %}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm view-toggle" data-view="card" data-list-id="{{ list_id }}">
|
<button type="button" class="view-toggle inline-flex items-center gap-2 px-4 py-2 text-sm font-medium border border-l-0 transition" data-view="card" data-list-id="{{ list_id }}">
|
||||||
<i class="fas fa-th me-1"></i> {% trans "Card" %}
|
<i data-lucide="layout-grid" class="w-4 h-4"></i> {% trans "Card" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* --- View Switcher Styles (Consolidated & Corrected) --- */
|
|
||||||
|
|
||||||
/* View Toggle Styles */
|
/* View Toggle Styles */
|
||||||
.view-toggle {
|
.view-toggle {
|
||||||
border-radius: 0.25rem;
|
border-color: #d1d5db;
|
||||||
margin-right: 0.25rem;
|
color: #4b5563;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
.view-toggle:hover {
|
||||||
|
background-color: #f3f4f6;
|
||||||
}
|
}
|
||||||
.view-toggle.active {
|
.view-toggle.active {
|
||||||
background-color: var(--kaauh-teal);
|
background-color: #b91c1c;
|
||||||
border-color: var(--kaauh-teal);
|
border-color: #b91c1c;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
.view-toggle.active:hover {
|
.view-toggle.active:hover {
|
||||||
background-color: var(--kaauh-teal-dark);
|
background-color: #991b1b;
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide elements by default */
|
/* Hide elements by default */
|
||||||
.table-view,
|
.table-view,
|
||||||
.card-view {
|
.card-view {
|
||||||
display: none !important; /* Use !important to ensure hiding works */
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Show active view */
|
/* Show active view */
|
||||||
.table-view.active {
|
.table-view.active {
|
||||||
display: block !important;
|
display: block;
|
||||||
}
|
}
|
||||||
.card-view.active {
|
.card-view.active {
|
||||||
/* Rely on the 'row' class which uses display: flex for proper column alignment. */
|
display: flex;
|
||||||
display: flex !important; /* rows often use display: flex in Bootstrap */
|
flex-wrap: wrap;
|
||||||
flex-wrap: wrap !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card View Styles */
|
/* Card View Styles */
|
||||||
.card-view .card {
|
.card-view .card {
|
||||||
border: 1px solid var(--kaauh-border);
|
border: 1px solid #e5e7eb;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||||
background-color: white;
|
background-color: white;
|
||||||
@ -56,7 +56,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
.card-view .card-header {
|
.card-view .card-header {
|
||||||
background-color: var(--kaauh-teal-dark);
|
background-color: #b91c1c;
|
||||||
color: white;
|
color: white;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 1rem 1.25rem;
|
padding: 1rem 1.25rem;
|
||||||
@ -67,24 +67,20 @@
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
.card-view .card-title {
|
.card-view .card-title {
|
||||||
color: var(--kaauh-teal-dark);
|
color: #b91c1c;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.card-view .card-text {
|
.card-view .card-text {
|
||||||
color: var(--kaauh-primary-text);
|
color: #374151;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
.card-view .card-footer {
|
.card-view .card-footer {
|
||||||
padding: 0.75rem 1.25rem;
|
padding: 0.75rem 1.25rem;
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
border-top: 1px solid var(--kaauh-border);
|
border-top: 1px solid #e5e7eb;
|
||||||
border-radius: 0 0 0.75rem 0.75rem;
|
border-radius: 0 0 0.75rem 0.75rem;
|
||||||
}
|
}
|
||||||
.card-view .btn-sm {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
padding: 0.3rem 0.6rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Table View Styles */
|
/* Table View Styles */
|
||||||
.table-view .table-responsive {
|
.table-view .table-responsive {
|
||||||
@ -93,48 +89,43 @@
|
|||||||
}
|
}
|
||||||
.table-view .table {
|
.table-view .table {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
.table-view .table thead th {
|
.table-view .table thead th {
|
||||||
background-color: var(--kaauh-teal-dark);
|
background-color: #b91c1c;
|
||||||
color: white;
|
color: white;
|
||||||
border-color: var(--kaauh-border);
|
border-color: #e5e7eb;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
.table-view .table tbody td {
|
.table-view .table tbody td {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
border-color: var(--kaauh-border);
|
border-color: #e5e7eb;
|
||||||
}
|
}
|
||||||
.table-view .table tbody tr {
|
.table-view .table tbody tr {
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
/* NOTE: We need to assume var(--kaauh-gray-light) is defined in base.html or job_list.html */
|
|
||||||
.table-view .table tbody tr:hover {
|
.table-view .table tbody tr:hover {
|
||||||
background-color: #f0f0f0; /* Fallback color for hover */
|
background-color: #f9fafb;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const listContainer = document.getElementById('{{ list_id }}');
|
const listContainer = document.getElementById('{{ list_id }}');
|
||||||
if (!listContainer) return; // Exit if container isn't found
|
if (!listContainer) return;
|
||||||
|
|
||||||
// Get list ID from the data attribute (or use the variable from Django context if the button is loaded)
|
|
||||||
const listId = listContainer.id;
|
const listId = listContainer.id;
|
||||||
|
|
||||||
// Get saved view preference from localStorage
|
|
||||||
const savedView = localStorage.getItem(`list_view_${listId}`) || 'table';
|
const savedView = localStorage.getItem(`list_view_${listId}`) || 'table';
|
||||||
|
|
||||||
// Set initial view
|
|
||||||
setView(savedView);
|
setView(savedView);
|
||||||
|
|
||||||
// Add click event listeners to view toggle buttons
|
|
||||||
document.querySelectorAll('.view-toggle').forEach(button => {
|
document.querySelectorAll('.view-toggle').forEach(button => {
|
||||||
// Ensure the button belongs to this list (optional check)
|
|
||||||
if (button.getAttribute('data-list-id') === listId) {
|
if (button.getAttribute('data-list-id') === listId) {
|
||||||
button.addEventListener('click', function() {
|
button.addEventListener('click', function() {
|
||||||
const view = this.getAttribute('data-view');
|
const view = this.getAttribute('data-view');
|
||||||
@ -144,7 +135,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function setView(view) {
|
function setView(view) {
|
||||||
// Update button states
|
|
||||||
document.querySelectorAll('.view-toggle[data-list-id="{{ list_id }}"]').forEach(button => {
|
document.querySelectorAll('.view-toggle[data-list-id="{{ list_id }}"]').forEach(button => {
|
||||||
if (button.getAttribute('data-view') === view) {
|
if (button.getAttribute('data-view') === view) {
|
||||||
button.classList.add('active');
|
button.classList.add('active');
|
||||||
@ -153,11 +143,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update view visibility
|
|
||||||
const tableView = listContainer.querySelector('.table-view');
|
const tableView = listContainer.querySelector('.table-view');
|
||||||
const cardView = listContainer.querySelector('.card-view');
|
const cardView = listContainer.querySelector('.card-view');
|
||||||
|
|
||||||
// Apply active class to the correct container
|
|
||||||
if (view === 'table') {
|
if (view === 'table') {
|
||||||
if (tableView) tableView.classList.add('active');
|
if (tableView) tableView.classList.add('active');
|
||||||
if (cardView) cardView.classList.remove('active');
|
if (cardView) cardView.classList.remove('active');
|
||||||
@ -166,8 +154,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (cardView) cardView.classList.add('active');
|
if (cardView) cardView.classList.add('active');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save preference to localStorage
|
|
||||||
localStorage.setItem(`list_view_${listId}`, view);
|
localStorage.setItem(`list_view_${listId}`, view);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize Lucide icons
|
||||||
|
lucide.createIcons();
|
||||||
</script>
|
</script>
|
||||||
@ -1,7 +1,35 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% url 'update_application_exam_status' slug=application.slug as url %}
|
{% url 'update_application_exam_status' slug=application.slug as url %}
|
||||||
<form data-on-submit="@post('{{url}}', {contentType: 'form', headers: {'X-CSRFToken': '{{ csrf_token }}'}})">
|
<form method="post" action="{{ url }}" data-on-submit="@post('{{url}}', {contentType: 'form', headers: {'X-CSRFToken': '{{ csrf_token }}'}})">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form.as_p }}
|
<div class="space-y-4">
|
||||||
<button type="submit" class="btn btn-primary">{% trans "Update" %}</button>
|
<div>
|
||||||
|
<label for="id_exam_status" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Exam Status" %}</label>
|
||||||
|
<select name="exam_status" id="id_exam_status" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
|
<option value="pending" {% if application.exam_status == 'pending' %}selected{% endif %}>{% trans "Pending" %}</option>
|
||||||
|
<option value="passed" {% if application.exam_status == 'passed' %}selected{% endif %}>{% trans "Passed" %}</option>
|
||||||
|
<option value="failed" {% if application.exam_status == 'failed' %}selected{% endif %}>{% trans "Failed" %}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if form.exam_score %}
|
||||||
|
<div>
|
||||||
|
<label for="id_exam_score" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Exam Score" %}</label>
|
||||||
|
<input type="number" name="exam_score" id="id_exam_score" value="{{ application.exam_score|default:'' }}" min="0" max="100" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if form.exam_notes %}
|
||||||
|
<div>
|
||||||
|
<label for="id_exam_notes" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Exam Notes" %}</label>
|
||||||
|
<textarea name="exam_notes" id="id_exam_notes" rows="3" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">{{ application.exam_notes|default:'' }}</textarea>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2.5 rounded-lg text-sm transition shadow-sm hover:shadow-md">
|
||||||
|
{% trans "Update" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -2,88 +2,91 @@
|
|||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
|
|
||||||
{% if LANGUAGE_CODE == 'en' %}
|
{% if LANGUAGE_CODE == 'en' %}
|
||||||
<h5> {% trans "AI Score" %}: <span class="badge bg-success"><i class="fas fa-robot me-1"></i> {{ application.match_score }}%</span> <span class="badge bg-success"><i class="fas fa-graduation-cap me-1"></i> {{ application.professional_category }} </span></h5>
|
<h5 class="text-lg font-bold text-gray-900 mb-4">{% trans "AI Score" %}:
|
||||||
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium"><i data-lucide="bot" class="w-3.5 h-3.5"></i> {{ application.match_score }}%</span>
|
||||||
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium"><i data-lucide="graduation-cap" class="w-3.5 h-3.5"></i> {{ application.professional_category }}</span>
|
||||||
|
</h5>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-briefcase me-2 text-primary"></i>
|
<i data-lucide="briefcase" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Job Fit" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Job Fit" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1">{{ application.job_fit_narrative }}</p>
|
<p class="text-gray-700 text-sm">{{ application.job_fit_narrative }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-star me-2 text-warning"></i>
|
<i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Top Keywords" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Top Keywords" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-2">
|
||||||
{% for keyword in application.top_3_keywords %}
|
{% for keyword in application.top_3_keywords %}
|
||||||
<span class="badge bg-info text-dark me-1">{{ keyword }}</span>
|
<span class="inline-block px-2.5 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">{{ keyword }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-clock me-2 text-info"></i>
|
<i data-lucide="clock" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Experience" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Experience" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1"><strong>{{ application.years_of_experience }}</strong> {% trans "years" %}</p>
|
<p class="text-sm mb-1"><strong class="text-gray-900">{{ application.years_of_experience }}</strong> {% trans "years" %}</p>
|
||||||
<p class="mb-0"><strong>{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title }}</p>
|
<p class="text-sm text-gray-700"><strong class="text-gray-900">{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-chart-line me-2 text-success"></i>
|
<i data-lucide="trending-up" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Skills" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Skills" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
|
<p class="text-sm mb-1"><strong class="text-gray-900">{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
|
||||||
<p class="mb-0"><strong>{% trans "Industry Match:" %}</strong>
|
<p class="text-sm"><strong class="text-gray-900">{% trans "Industry Match:" %}</strong>
|
||||||
<span class="badge {% if application.industry_match_score >= 70 %}bg-success{% elif application.industry_match_score >= 40 %}bg-warning{% else %}bg-danger{% endif %}">
|
<span class="inline-block px-2.5 py-1 rounded-full text-xs font-medium {% if application.industry_match_score >= 70 %}bg-green-100 text-green-800{% elif application.industry_match_score >= 40 %}bg-yellow-100 text-yellow-800{% else %}bg-red-100 text-red-800{% endif %}">
|
||||||
{{ application.industry_match_score }}%
|
{{ application.industry_match_score }}%
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="message-square" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Recommendation" %}</label>
|
||||||
<textarea class="form-control" rows="6" readonly>{{ application.recommendation }}</textarea>
|
<textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="6" readonly>{{ application.recommendation }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="thumbs-up" class="w-4 h-4 inline mr-1 text-green-600"></i> {% trans "Strengths" %}</label>
|
||||||
<textarea class="form-control" rows="4" readonly>{{ application.strengths }}</textarea>
|
<textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="4" readonly>{{ application.strengths }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="thumbs-down" class="w-4 h-4 inline mr-1 text-red-600"></i> {% trans "Weaknesses" %}</label>
|
||||||
<textarea class="form-control" rows="4" readonly>{{ application.weaknesses }}</textarea>
|
<textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="4" readonly>{{ application.weaknesses }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-list-check me-1"></i> {% trans "Criteria Assessment" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="check-square" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Criteria Assessment" %}</label>
|
||||||
<div class="table-responsive">
|
<div class="overflow-hidden rounded-lg border border-gray-200">
|
||||||
<table class="table table-sm">
|
<table class="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="bg-temple-red text-white">
|
||||||
<th>{% trans "Criteria" %}</th>
|
<th class="px-4 py-3 text-left font-semibold">{% trans "Criteria" %}</th>
|
||||||
<th>{% trans "Status" %}</th>
|
<th class="px-4 py-3 text-left font-semibold">{% trans "Status" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
{% for criterion, status in application.criteria_checklist.items %}
|
{% for criterion, status in application.criteria_checklist.items %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50">
|
||||||
<td>{{ criterion }}</td>
|
<td class="px-4 py-3 text-gray-900">{{ criterion }}</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
{% if status == "Met" %}
|
{% if status == "Met" %}
|
||||||
<span class="badge bg-success"><i class="fas fa-check me-1"></i> {% trans "Met" %}</span>
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i data-lucide="check" class="w-3 h-3"></i> {% trans "Met" %}</span>
|
||||||
{% elif status == "Not Met" %}
|
{% elif status == "Not Met" %}
|
||||||
<span class="badge bg-danger"><i class="fas fa-times me-1"></i> {% trans "Not Met" %}</span>
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium"><i data-lucide="x" class="w-3 h-3"></i> {% trans "Not Met" %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">{{ status }}</span>
|
<span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ status }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -93,121 +96,124 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-check-circle me-2 text-success"></i>
|
<i data-lucide="check-circle" class="w-4 h-4 text-green-600"></i>
|
||||||
<small class="text-muted">{% trans "Minimum Requirements" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Minimum Requirements" %}</small>
|
||||||
</div>
|
</div>
|
||||||
{% if application.min_requirements_met %}
|
{% if application.min_requirements_met %}
|
||||||
<span class="badge bg-success">{% trans "Met" %}</span>
|
<span class="inline-block px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium">{% trans "Met" %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-danger">{% trans "Not Met" %}</span>
|
<span class="inline-block px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium">{% trans "Not Met" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-star me-2 text-warning"></i>
|
<i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Screening Rating" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Screening Rating" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<span class="badge bg-secondary">{{ application.screening_stage_rating }}</span>
|
<span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ application.screening_stage_rating }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if application.language_fluency %}
|
{% if application.language_fluency %}
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="globe" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Language Fluency" %}</label>
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{% for language in application.language_fluency %}
|
{% for language in application.language_fluency %}
|
||||||
<span class="badge bg-light text-dark">{{ language }}</span>
|
<span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ language }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<h5> {% trans "AI Score" %}: <span class="badge bg-success"><i class="fas fa-robot me-1"></i> {{ application.match_score }}%</span> <span class="badge bg-success"><i class="fas fa-graduation-cap me-1"></i> {{ application.professional_category_ar }} </span></h5>
|
<h5 class="text-lg font-bold text-gray-900 mb-4">{% trans "AI Score" %}:
|
||||||
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium"><i data-lucide="bot" class="w-3.5 h-3.5"></i> {{ application.match_score }}%</span>
|
||||||
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-sm font-medium"><i data-lucide="graduation-cap" class="w-3.5 h-3.5"></i> {{ application.professional_category_ar }}</span>
|
||||||
|
</h5>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-briefcase me-2 text-primary"></i>
|
<i data-lucide="briefcase" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Job Fit" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Job Fit" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1">{{ application.job_fit_narrative_ar }}</p>
|
<p class="text-gray-700 text-sm">{{ application.job_fit_narrative_ar }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-star me-2 text-warning"></i>
|
<i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Top Keywords" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Top Keywords" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-2">
|
||||||
{% for keyword in application.top_3_keywords_ar %}
|
{% for keyword in application.top_3_keywords_ar %}
|
||||||
<span class="badge bg-info text-dark me-1">{{ keyword }}</span>
|
<span class="inline-block px-2.5 py-1 bg-blue-100 text-blue-800 rounded-full text-xs font-medium">{{ keyword }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-clock me-2 text-info"></i>
|
<i data-lucide="clock" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Experience" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Experience" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1"><strong>{{ application.years_of_experience }}</strong> {% trans "years" %}</p>
|
<p class="text-sm mb-1"><strong class="text-gray-900">{{ application.years_of_experience }}</strong> {% trans "years" %}</p>
|
||||||
<p class="mb-0"><strong>{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title_ar }}</p>
|
<p class="text-sm text-gray-700"><strong class="text-gray-900">{% trans "Recent Role:" %}</strong> {{ application.most_recent_job_title_ar }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-chart-line me-2 text-success"></i>
|
<i data-lucide="trending-up" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Skills" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Skills" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-1"><strong>{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
|
<p class="text-sm mb-1"><strong class="text-gray-900">{% trans "Soft Skills:" %}</strong> {{ application.soft_skills_score }}%</p>
|
||||||
<p class="mb-0"><strong>{% trans "Industry Match:" %}</strong>
|
<p class="text-sm"><strong class="text-gray-900">{% trans "Industry Match:" %}</strong>
|
||||||
<span class="badge {% if application.industry_match_score >= 70 %}bg-success{% elif application.industry_match_score >= 40 %}bg-warning{% else %}bg-danger{% endif %}">
|
<span class="inline-block px-2.5 py-1 rounded-full text-xs font-medium {% if application.industry_match_score >= 70 %}bg-green-100 text-green-800{% elif application.industry_match_score >= 40 %}bg-yellow-100 text-yellow-800{% else %}bg-red-100 text-red-800{% endif %}">
|
||||||
{{ application.industry_match_score }}%
|
{{ application.industry_match_score }}%
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-comment me-1 text-info"></i> {% trans "Recommendation" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="message-square" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Recommendation" %}</label>
|
||||||
<textarea class="form-control" rows="6" readonly>{{ application.recommendation_ar }}</textarea>
|
<textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="6" readonly>{{ application.recommendation_ar }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-thumbs-up me-1 text-success"></i> {% trans "Strengths" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="thumbs-up" class="w-4 h-4 inline mr-1 text-green-600"></i> {% trans "Strengths" %}</label>
|
||||||
<textarea class="form-control" rows="4" readonly>{{ application.strengths_ar }}</textarea>
|
<textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="4" readonly>{{ application.strengths_ar }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-thumbs-down me-1 text-danger"></i> {% trans "Weaknesses" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="thumbs-down" class="w-4 h-4 inline mr-1 text-red-600"></i> {% trans "Weaknesses" %}</label>
|
||||||
<textarea class="form-control" rows="4" readonly>{{ application.weaknesses_ar }}</textarea>
|
<textarea class="w-full px-4 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-700 text-sm" rows="4" readonly>{{ application.weaknesses_ar }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-list-check me-1"></i> {% trans "Criteria Assessment" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="check-square" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Criteria Assessment" %}</label>
|
||||||
<div class="table-responsive">
|
<div class="overflow-hidden rounded-lg border border-gray-200">
|
||||||
<table class="table table-sm">
|
<table class="w-full text-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="bg-temple-red text-white">
|
||||||
<th>{% trans "Criteria" %}</th>
|
<th class="px-4 py-3 text-left font-semibold">{% trans "Criteria" %}</th>
|
||||||
<th>{% trans "Status" %}</th>
|
<th class="px-4 py-3 text-left font-semibold">{% trans "Status" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="bg-white divide-y divide-gray-200">
|
||||||
{% for criterion, status in application.criteria_checklist_ar.items %}
|
{% for criterion, status in application.criteria_checklist_ar.items %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50">
|
||||||
<td>{{ criterion }}</td>
|
<td class="px-4 py-3 text-gray-900">{{ criterion }}</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
{% if status == "Met" %}
|
{% if status == "Met" %}
|
||||||
<span class="badge bg-success"><i class="fas fa-check me-1"></i> {% trans "Met" %}</span>
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i data-lucide="check" class="w-3 h-3"></i> {% trans "Met" %}</span>
|
||||||
{% elif status == "Not Met" %}
|
{% elif status == "Not Met" %}
|
||||||
<span class="badge bg-danger"><i class="fas fa-times me-1"></i> {% trans "Not Met" %}</span>
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium"><i data-lucide="x" class="w-3 h-3"></i> {% trans "Not Met" %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-secondary">{{ status }}</span>
|
<span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ status }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -217,36 +223,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mb-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-check-circle me-2 text-success"></i>
|
<i data-lucide="check-circle" class="w-4 h-4 text-green-600"></i>
|
||||||
<small class="text-muted">{% trans "Minimum Requirements" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Minimum Requirements" %}</small>
|
||||||
</div>
|
</div>
|
||||||
{% if application.min_requirements_met_ar %}
|
{% if application.min_requirements_met_ar %}
|
||||||
<span class="badge bg-success">{% trans "Met" %}</span>
|
<span class="inline-block px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium">{% trans "Met" %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-danger">{% trans "Not Met" %}</span>
|
<span class="inline-block px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium">{% trans "Not Met" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="bg-gray-50 rounded-xl p-4">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-star me-2 text-warning"></i>
|
<i data-lucide="star" class="w-4 h-4 text-temple-red"></i>
|
||||||
<small class="text-muted">{% trans "Screening Rating" %}</small>
|
<small class="text-gray-600 font-medium">{% trans "Screening Rating" %}</small>
|
||||||
</div>
|
</div>
|
||||||
<span class="badge bg-secondary">{{ application.screening_stage_rating_ar }}</span>
|
<span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ application.screening_stage_rating_ar }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if application.language_fluency_ar %}
|
{% if application.language_fluency_ar %}
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label class="form-label"><i class="fas fa-language me-1 text-info"></i> {% trans "Language Fluency" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2"><i data-lucide="globe" class="w-4 h-4 inline mr-1 text-temple-red"></i> {% trans "Language Fluency" %}</label>
|
||||||
<div class="d-flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{% for language in application.language_fluency_ar %}
|
{% for language in application.language_fluency_ar %}
|
||||||
<span class="badge bg-light text-dark">{{ language }}</span>
|
<span class="inline-block px-2.5 py-1 bg-gray-100 text-gray-800 rounded-full text-xs font-medium">{{ language }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
@ -1,34 +1,35 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<form id="exam-update-form" hx-post="{% url 'update_application_status' job.slug application.slug 'exam' 'Failed' %}" hx-swap='outerHTML' hx-target="#status-result-{{ application.pk }}"
|
<form id="exam-update-form" hx-post="{% url 'update_application_status' job.slug application.slug 'exam' 'Failed' %}" hx-swap='outerHTML' hx-target="#status-result-{{ application.pk }}"
|
||||||
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
|
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
|
||||||
<div class="d-flex justify-content-center align-items-center gap-2">
|
<div class="flex justify-center items-center gap-4 mb-4">
|
||||||
<div class="form-check d-flex align-items-center gap-2">
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
<input class="form-check-input" type="radio" name="exam_status" id="exam_passed" value="Passed" {% if application.exam_status == 'Passed' %}checked{% endif %}>
|
<input type="radio" name="exam_status" id="exam_passed" value="Passed" {% if application.exam_status == 'Passed' %}checked{% endif %} class="w-4 h-4 text-temple-red border-gray-300 focus:ring-temple-red/20">
|
||||||
<label class="form-check-label" for="exam_passed">
|
<span class="text-gray-700 text-sm"><i data-lucide="check" class="w-4 h-4 inline text-green-600"></i> {% trans "Passed" %}</span>
|
||||||
<i class="fas fa-check me-1"></i> {% trans "Passed" %}
|
</label>
|
||||||
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="radio" name="exam_status" id="exam_failed" value="Failed" {% if application.exam_status == 'Failed' %}checked{% endif %} class="w-4 h-4 text-temple-red border-gray-300 focus:ring-temple-red/20">
|
||||||
|
<span class="text-gray-700 text-sm"><i data-lucide="x" class="w-4 h-4 inline text-red-600"></i> {% trans "Failed" %}</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check d-flex align-items-center gap-2">
|
|
||||||
<input class="form-check-input" type="radio" name="exam_status" id="exam_failed" value="Failed" {% if application.exam_status == 'Failed' %}checked{% endif %}>
|
<div class="flex justify-center items-center mt-4 gap-4">
|
||||||
<label class="form-check-label" for="exam_failed">
|
<div class="w-24 text-right">
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Failed" %}
|
<label for="exam_score" class="block text-sm font-medium text-gray-600">{% trans "Exam Score" %}</label>
|
||||||
</label>
|
</div>
|
||||||
|
<div class="w-24">
|
||||||
|
<input type="number" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="exam_score" name="exam_score" min="0" max="100" required value="{{ application.exam_score }}">
|
||||||
|
</div>
|
||||||
|
<div class="w-24 text-left">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-center align-items-center mt-3 gap-2">
|
|
||||||
<div class="w-25 text-end pe-none">
|
<div class="text-center mt-4">
|
||||||
<label for="exam_score" class="form-label small text-muted">{% trans "Exam Score" %}</label>
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2 rounded-lg text-sm transition shadow-sm hover:shadow-md">
|
||||||
</div>
|
<i data-lucide="check" class="w-4 h-4"></i> {% trans "Update" %}
|
||||||
<div class="w-25">
|
|
||||||
<input type="number" class="form-control form-control-sm" id="exam_score" name="exam_score" min="0" max="100" required value="{{ application.exam_score }}">
|
|
||||||
</div>
|
|
||||||
<div class="w-25 text-start ps-none">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="text-center mt-3">
|
|
||||||
<button type="submit" class="btn btn-success btn-sm">
|
|
||||||
<i class="fas fa-check me-1"></i> {% trans "Update" %}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
@ -1,10 +1,14 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="d-flex justify-content-center align-items-center gap-2" hx-swap='outerHTML' hx-target="#interview-result-{{ application.pk }}"
|
<div class="flex justify-center items-center gap-3" hx-swap='outerHTML' hx-target="#interview-result-{{ application.pk }}"
|
||||||
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
|
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
|
||||||
<a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Passed' %}" class="btn btn-outline-secondary">
|
<a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Passed' %}" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-300 text-gray-700 hover:border-temple-red hover:text-temple-red rounded-lg text-sm font-medium transition">
|
||||||
<i class="fas fa-check me-1"></i> {% trans "Passed" %}
|
<i data-lucide="check" class="w-4 h-4"></i> {% trans "Passed" %}
|
||||||
</a>
|
</a>
|
||||||
<a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Failed' %}" class="btn btn-danger">
|
<a hx-post="{% url 'update_application_status' job.slug application.slug 'interview' 'Failed' %}" class="inline-flex items-center gap-2 px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-medium transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Failed" %}
|
<i data-lucide="x" class="w-4 h-4"></i> {% trans "Failed" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
@ -1,10 +1,14 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="d-flex justify-content-center align-items-center gap-2" hx-swap='outerHTML' hx-target="#status-result-{{ application.pk }}"
|
<div class="flex justify-center items-center gap-3" hx-swap='outerHTML' hx-target="#status-result-{{ application.pk }}"
|
||||||
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
|
hx-on::after-request="const modal = bootstrap.Modal.getInstance(document.getElementById('candidateviewModal')); if (modal) { modal.hide(); }">
|
||||||
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}" class="btn btn-outline-secondary">
|
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Accepted' %}" class="inline-flex items-center gap-2 px-4 py-2 border-2 border-gray-300 text-gray-700 hover:border-temple-red hover:text-temple-red rounded-lg text-sm font-medium transition">
|
||||||
<i class="fas fa-check me-1"></i> {% trans "Accepted" %}
|
<i data-lucide="check" class="w-4 h-4"></i> {% trans "Accepted" %}
|
||||||
</a>
|
</a>
|
||||||
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Rejected' %}" class="btn btn-danger">
|
<a hx-post="{% url 'update_application_status' job.slug application.slug 'offer' 'Rejected' %}" class="inline-flex items-center gap-2 px-4 py-2 bg-red-500 hover:bg-red-600 text-white rounded-lg text-sm font-medium transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Rejected" %}
|
<i data-lucide="x" class="w-4 h-4"></i> {% trans "Rejected" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
@ -1,541 +1,209 @@
|
|||||||
<!DOCTYPE html>
|
{% load i18n %}
|
||||||
<html lang="en">
|
<div class="max-w-7xl mx-auto bg-white rounded-3xl overflow-hidden shadow-2xl">
|
||||||
<head>
|
<!-- Header -->
|
||||||
<meta charset="UTF-8">
|
<div class="bg-gradient-to-br from-temple-red to-[#004a53] text-white p-10 relative overflow-hidden">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<div class="relative z-10">
|
||||||
<title>Resume - Abdullah Sami Bakhsh</title>
|
<h1 class="text-5xl font-bold mb-2.5">
|
||||||
<style>
|
{% if application.name %}{{ application.name }}{% else %}{% trans "Candidate" %}{% endif %}
|
||||||
:root {
|
</h1>
|
||||||
--kaauh-teal: #00636e;
|
<div class="text-xl opacity-90 mb-5">{% trans "Resume Analysis" %}</div>
|
||||||
--kaauh-teal-dark: #004a53;
|
<div class="flex flex-wrap gap-5 text-base">
|
||||||
--kaauh-light-bg: #f9fbfd;
|
{% if application.email %}
|
||||||
--kaauh-border: #eaeff3;
|
<div class="flex items-center gap-2">
|
||||||
--text-primary: #2c3e50;
|
<span class="text-lg">📱</span>
|
||||||
--text-secondary: #6c757d;
|
<span>{{ application.email }}</span>
|
||||||
--white: #ffffff;
|
|
||||||
--danger: #dc3545;
|
|
||||||
--warning: #ffc107;
|
|
||||||
--success: #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-light-bg) 0%, #e3f2fd 100%);
|
|
||||||
color: var(--text-primary);
|
|
||||||
line-height: 1.6;
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resume-container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: var(--white);
|
|
||||||
border-radius: 20px;
|
|
||||||
box-shadow: 0 20px 60px rgba(0, 99, 110, 0.1);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
|
||||||
color: var(--white);
|
|
||||||
padding: 40px;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: -50%;
|
|
||||||
right: -10%;
|
|
||||||
width: 500px;
|
|
||||||
height: 500px;
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
font-size: 2.5em;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 1.3em;
|
|
||||||
opacity: 0.9;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-info {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 20px;
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 350px;
|
|
||||||
gap: 30px;
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left-column {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right-column {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
background: var(--white);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 25px;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 25px rgba(0, 99, 110, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 1.3em;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-teal);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 2px solid var(--kaauh-teal);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.summary {
|
|
||||||
line-height: 1.8;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.experience-item {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.experience-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.job-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.job-title {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.job-period {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.9em;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.achievements {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.achievements li {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.education-item {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
padding-bottom: 15px;
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.education-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.degree {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.institution {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skill-tag {
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
|
||||||
color: var(--white);
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 20px;
|
|
||||||
font-size: 0.85em;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.language-name {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proficiency {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-card {
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
|
||||||
color: var(--white);
|
|
||||||
text-align: center;
|
|
||||||
padding: 30px 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.match-score {
|
|
||||||
font-size: 3em;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-label {
|
|
||||||
font-size: 1.1em;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recommendation {
|
|
||||||
background: var(--kaauh-light-bg);
|
|
||||||
border-left: 4px solid var(--danger);
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recommendation-title {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--danger);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recommendation-text {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.keyword-tag {
|
|
||||||
background: var(--kaauh-light-bg);
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
padding: 4px 10px;
|
|
||||||
border-radius: 15px;
|
|
||||||
font-size: 0.85em;
|
|
||||||
font-weight: 500;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.strengths-weaknesses {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.strength-box {
|
|
||||||
background: rgba(40, 167, 69, 0.1);
|
|
||||||
border: 1px solid var(--success);
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.weakness-box {
|
|
||||||
background: rgba(220, 53, 69, 0.1);
|
|
||||||
border: 1px solid var(--danger);
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.box-title {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.strength-box .box-title {
|
|
||||||
color: var(--success);
|
|
||||||
}
|
|
||||||
|
|
||||||
.weakness-box .box-title {
|
|
||||||
color: var(--danger);
|
|
||||||
}
|
|
||||||
|
|
||||||
.box-content {
|
|
||||||
font-size: 0.9em;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 968px) {
|
|
||||||
.main-content {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.strengths-weaknesses {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
|
||||||
.name {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-content {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.job-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="resume-container">
|
|
||||||
<div class="header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1 class="name">Abdullah Sami Bakhsh</h1>
|
|
||||||
<div class="title">Head, Recruitment & Training</div>
|
|
||||||
<div class="contact-info">
|
|
||||||
<div class="contact-item">
|
|
||||||
<span>📱</span>
|
|
||||||
<span>+966 561 168 180</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="contact-item">
|
{% endif %}
|
||||||
<span>✉️</span>
|
{% if application.phone %}
|
||||||
<span>Bakhsh.Abdullah@Outlook.com</span>
|
<div class="flex items-center gap-2">
|
||||||
</div>
|
<span class="text-lg">✉️</span>
|
||||||
<div class="contact-item">
|
<span>{{ application.phone }}</span>
|
||||||
<span>📍</span>
|
|
||||||
<span>Saudi Arabia</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if application.location %}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-lg">📍</span>
|
||||||
|
<span>{{ application.location }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="main-content">
|
<!-- Main Content -->
|
||||||
<div class="left-column">
|
<div class="grid grid-cols-1 lg:grid-cols-[1fr_350px] gap-8 p-10">
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">
|
<!-- Left Column -->
|
||||||
<span>📋</span> Professional Summary
|
<div class="flex flex-col gap-8">
|
||||||
|
<!-- Professional Summary -->
|
||||||
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>📋</span> {% trans "Professional Summary" %}
|
||||||
</h2>
|
</h2>
|
||||||
<p class="summary">
|
<p class="text-gray-600 leading-8">
|
||||||
Strategic and 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.
|
{% if application.ai_analysis_data %}
|
||||||
|
{{ application.ai_analysis_data.parsed_summary }}
|
||||||
|
{% elif application.parsed_summary %}
|
||||||
|
{{ application.parsed_summary }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-gray-400 italic">{% trans "No summary available" %}</span>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<!-- Work Experience -->
|
||||||
<h2 class="section-title">
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
<span>💼</span> Work Experience
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>💼</span> {% trans "Work Experience" %}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="experience-item">
|
|
||||||
<div class="job-header">
|
{% if application.ai_analysis_data.work_experience %}
|
||||||
|
{% for exp in application.ai_analysis_data.work_experience %}
|
||||||
|
<div class="mb-6 pb-5 border-b border-gray-200 last:border-0 last:mb-0 last:pb-0">
|
||||||
|
<div class="flex justify-between items-start mb-2.5">
|
||||||
<div>
|
<div>
|
||||||
<div class="job-title">Head, Recruitment & Training</div>
|
<div class="font-bold text-gray-900 text-lg">{{ exp.title }}</div>
|
||||||
<div class="company">TASNEE - DOWNSTREAM & METALLURGY SBUs</div>
|
<div class="text-gray-600 font-medium">{{ exp.company }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="job-period">Oct 2024 - Present</div>
|
<div class="text-gray-500 text-sm whitespace-nowrap">{{ exp.period }}</div>
|
||||||
</div>
|
</div>
|
||||||
<ul class="achievements">
|
<ul class="list-disc list-inside pl-5 text-gray-600 space-y-1.5">
|
||||||
<li>Led recruitment and training initiatives across downstream and metallurgical divisions</li>
|
{% for responsibility in exp.responsibilities %}
|
||||||
<li>Developed workforce analytics program</li>
|
<li>{{ responsibility }}</li>
|
||||||
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
<div class="experience-item">
|
{% else %}
|
||||||
<div class="job-header">
|
<p class="text-gray-400 italic">{% trans "No work experience data available" %}</p>
|
||||||
<div>
|
{% endif %}
|
||||||
<div class="job-title">Human Resources Business Partner</div>
|
|
||||||
<div class="company">TASNEE – METALLURGY SBU</div>
|
|
||||||
</div>
|
|
||||||
<div class="job-period">Oct 2015 - Present</div>
|
|
||||||
</div>
|
|
||||||
<ul class="achievements">
|
|
||||||
<li>Implemented HR strategies aligning with business goals</li>
|
|
||||||
<li>Optimized recruitment processes</li>
|
|
||||||
<li>Ensured regulatory compliance</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="experience-item">
|
<!-- Education -->
|
||||||
<div class="job-header">
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
<div>
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
<div class="job-title">Specialist, Recruitment</div>
|
<span>🎓</span> {% trans "Education" %}
|
||||||
<div class="company">MARAFIQ</div>
|
|
||||||
</div>
|
|
||||||
<div class="job-period">Jul 2011 - Feb 2013</div>
|
|
||||||
</div>
|
|
||||||
<ul class="achievements">
|
|
||||||
<li>Performed recruitment for various roles</li>
|
|
||||||
<li>Improved selection processes</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">
|
|
||||||
<span>🎓</span> Education
|
|
||||||
</h2>
|
</h2>
|
||||||
<div class="education-item">
|
|
||||||
<div class="degree">Associate Diploma in People Management Level 5</div>
|
{% if application.ai_analysis_data.education %}
|
||||||
<div class="institution">Chartered Institute of Personnel and Development (CIPD)</div>
|
{% for edu in application.ai_analysis_data.education %}
|
||||||
</div>
|
<div class="mb-4 pb-4 border-b border-gray-200 last:border-0 last:mb-0 last:pb-0">
|
||||||
<div class="education-item">
|
<div class="font-bold text-gray-900">{{ edu.degree }}</div>
|
||||||
<div class="degree">Bachelor's Degree in Chemical Engineering Technology</div>
|
<div class="text-gray-600">{{ edu.institution }}</div>
|
||||||
<div class="institution">Yanbu Industrial College</div>
|
<div class="text-gray-500 text-sm mt-1">{{ edu.year }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p class="text-gray-400 italic">{% trans "No education data available" %}</p>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="right-column">
|
<!-- Right Column -->
|
||||||
<div class="score-card">
|
<div class="flex flex-col gap-6">
|
||||||
<div class="match-score">10%</div>
|
<!-- Match Score -->
|
||||||
<div class="score-label">Match Score</div>
|
{% if application.ai_analysis_data %}
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white text-center p-8 rounded-2xl shadow-lg">
|
||||||
|
<div class="text-5xl font-bold mb-2.5">{{ application.ai_analysis_data.match_score }}%</div>
|
||||||
|
<div class="text-lg opacity-90">{% trans "Match Score" %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="section">
|
<!-- Assessment -->
|
||||||
<h2 class="section-title">
|
{% if application.ai_analysis_data %}
|
||||||
<span>🔍</span> Assessment
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>🔍</span> {% trans "Assessment" %}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="strengths-weaknesses">
|
|
||||||
<div class="strength-box">
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
|
||||||
<div class="box-title">Strengths</div>
|
<div class="bg-emerald-50 border border-emerald-500 p-4 rounded-xl">
|
||||||
<div class="box-content">Extensive HR leadership and project management experience</div>
|
<div class="font-bold text-emerald-700 mb-2 text-sm flex items-center gap-2">
|
||||||
|
<span>✅</span> {% trans "Strengths" %}
|
||||||
</div>
|
</div>
|
||||||
<div class="weakness-box">
|
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.strengths }}</div>
|
||||||
<div class="box-title">Weaknesses</div>
|
</div>
|
||||||
<div class="box-content">Lack of IT infrastructure, cybersecurity, and relevant certifications</div>
|
<div class="bg-red-50 border border-red-500 p-4 rounded-xl">
|
||||||
|
<div class="font-bold text-red-700 mb-2 text-sm flex items-center gap-2">
|
||||||
|
<span>❌</span> {% trans "Weaknesses" %}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.weaknesses }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="recommendation">
|
<div class="bg-temple-red/5 border-l-4 border-l-temple-red p-4 rounded-xl">
|
||||||
<div class="recommendation-title">Recommendation</div>
|
<div class="font-bold text-temple-red mb-2 flex items-center gap-2">
|
||||||
<div class="recommendation-text">Candidate does not meet the IT management requirements; not recommended for interview.</div>
|
<span>💡</span> {% trans "Recommendation" %}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.recommendation }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<div class="section">
|
<!-- Top Keywords -->
|
||||||
<h2 class="section-title">
|
{% if application.ai_analysis_data %}
|
||||||
<span>💡</span> Top Keywords
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>💡</span> {% trans "Top Keywords" %}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="skills-container">
|
<div class="flex flex-wrap gap-2">
|
||||||
<span class="keyword-tag">HR</span>
|
{% for keyword in application.ai_analysis_data.top_3_keywords %}
|
||||||
<span class="keyword-tag">Recruitment</span>
|
<span class="bg-temple-red/10 text-temple-red px-3 py-1.5 rounded-full text-sm font-medium border border-temple-red/20">
|
||||||
<span class="keyword-tag">Training</span>
|
{{ keyword }}
|
||||||
|
</span>
|
||||||
|
{% empty %}
|
||||||
|
<span class="text-gray-400 italic text-sm">{% trans "No keywords available" %}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Skills -->
|
||||||
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>🛠️</span> {% trans "Skills" %}
|
||||||
|
</h2>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{% if application.ai_analysis_data.skills %}
|
||||||
|
{% for skill in application.ai_analysis_data.skills %}
|
||||||
|
<span class="bg-gradient-to-r from-temple-red to-[#7a1a29] text-white px-3 py-1.5 rounded-full text-sm font-medium shadow-sm">
|
||||||
|
{{ skill }}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-gray-400 italic text-sm">{% trans "No skills data available" %}</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section">
|
<!-- Professional Details -->
|
||||||
<h2 class="section-title">
|
{% if application.ai_analysis_data %}
|
||||||
<span>🛠️</span> Skills
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>👤</span> {% trans "Professional Details" %}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="skills-container">
|
|
||||||
<span class="skill-tag">Workforce Analytics</span>
|
|
||||||
<span class="skill-tag">Succession Planning</span>
|
|
||||||
<span class="skill-tag">Organizational Development</span>
|
|
||||||
<span class="skill-tag">Recruitment & Selection</span>
|
|
||||||
<span class="skill-tag">Employee Engagement</span>
|
|
||||||
<span class="skill-tag">Training Needs Analysis</span>
|
|
||||||
<span class="skill-tag">Performance Management</span>
|
|
||||||
<span class="skill-tag">Time Management</span>
|
|
||||||
<span class="skill-tag">Negotiation Skills</span>
|
|
||||||
<span class="skill-tag">SAP & HRIS Systems</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
<div class="flex justify-between items-center mb-3">
|
||||||
<h2 class="section-title">
|
<span class="text-gray-600 text-sm">{% trans "Years of Experience:" %}</span>
|
||||||
<span>🌍</span> Languages
|
<strong class="text-gray-900">{{ application.ai_analysis_data.years_of_experience }}</strong>
|
||||||
</h2>
|
|
||||||
<div class="language-item">
|
|
||||||
<span class="language-name">Arabic</span>
|
|
||||||
<span class="proficiency">Native</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="language-item">
|
<div class="flex justify-between items-center mb-3">
|
||||||
<span class="language-name">English</span>
|
<span class="text-gray-600 text-sm">{% trans "Recent Job Title:" %}</span>
|
||||||
<span class="proficiency">Fluent</span>
|
<strong class="text-gray-900 text-sm text-right">{{ application.ai_analysis_data.most_recent_job_title }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="language-item">
|
<div class="flex justify-between items-center mb-3">
|
||||||
<span class="language-name">Japanese</span>
|
<span class="text-gray-600 text-sm">{% trans "Industry Match:" %}</span>
|
||||||
<span class="proficiency">Intermediate</span>
|
<span class="px-2.5 py-0.5 rounded-full text-xs font-bold {% if application.ai_analysis_data.experience_industry_match >= 70 %}bg-emerald-500 text-white{% elif application.ai_analysis_data.experience_industry_match >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
|
||||||
|
{{ application.ai_analysis_data.experience_industry_match }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-gray-600 text-sm">{% trans "Soft Skills Score:" %}</span>
|
||||||
|
<strong class="text-gray-900">{{ application.ai_analysis_data.soft_skills_score }}%</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,23 +1,26 @@
|
|||||||
<div class="card mt-4">
|
{% load i18n %}
|
||||||
<div class="card-header text-primary-theme">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
|
||||||
<h5 class="card-title mb-0">{% trans "Add Comment" %}</h5>
|
<div class="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-red mb-0">{% trans "Add Comment" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-4">
|
||||||
<form method="post" action="{% url 'add_meeting_comment' meeting.slug %}">
|
<form method="post" action="{% url 'add_meeting_comment' meeting.slug %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{{ form.content }}
|
{{ form.content }}
|
||||||
{% if form.content.errors %}
|
{% if form.content.errors %}
|
||||||
<div class="text-danger mt-1">
|
<div class="text-red-600 mt-1 text-sm">
|
||||||
{% for error in form.content.errors %}
|
{% for error in form.content.errors %}
|
||||||
<small>{{ error }}</small>
|
<small>{{ error }}</small>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">{% trans "Add Comment" %}</button>
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2 rounded-lg text-sm transition shadow-sm hover:shadow-md">
|
||||||
|
{% trans "Add Comment" %}
|
||||||
|
</button>
|
||||||
{% if 'HX-Request' in request.headers %}
|
{% if 'HX-Request' in request.headers %}
|
||||||
<button type="button" class="btn btn-secondary" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">Cancel</button>
|
<button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2 rounded-lg text-sm transition" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">{% trans "Cancel" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,56 +1,59 @@
|
|||||||
|
{% load i18n %}
|
||||||
<div id="comment-section">
|
<div id="comment-section">
|
||||||
<div class="card mt-4">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
|
||||||
<div class="card-header text-primary-theme d-flex justify-content-between align-items-center">
|
<div class="bg-gray-50 px-4 py-3 border-b border-gray-200 flex items-center justify-between">
|
||||||
<h5 class="card-title mb-0">{% trans "Comments" %} ({{ comments.count }})</h5>
|
<h5 class="text-lg font-bold text-temple-red mb-0">{% trans "Comments" %} ({{ comments.count }})</h5>
|
||||||
{% if 'HX-Request' in request.headers %}
|
{% if 'HX-Request' in request.headers %}
|
||||||
<button type="button" class="btn btn-light btn-sm" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">
|
<button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-3 py-1.5 rounded-lg text-sm transition" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">
|
||||||
<i class="bi bi-x-lg"></i> {% trans "Close" %}
|
<i data-lucide="x" class="w-4 h-4"></i> {% trans "Close" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-4">
|
||||||
{% if comments %}
|
{% if comments %}
|
||||||
<div class="row">
|
<div class="space-y-3">
|
||||||
{% for comment in comments %}
|
{% for comment in comments %}
|
||||||
<div class="col-12 mb-3">
|
<div class="bg-gray-50 rounded-xl overflow-hidden">
|
||||||
<div class="card">
|
<div class="px-4 py-3 bg-white border-b border-gray-100 flex items-center justify-between">
|
||||||
<div class="card-header d-flex justify-content-between align-items-start ">
|
|
||||||
<div>
|
<div>
|
||||||
<strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong>
|
<strong class="text-gray-900">{{ comment.author.get_full_name|default:comment.author.username }}</strong>
|
||||||
{% if comment.author != user %}
|
{% if comment.author != user %}
|
||||||
<span class="badge bg-secondary ms-2">{% trans "Comment" %}</span>
|
<span class="inline-block ml-2 px-2 py-0.5 bg-gray-200 text-gray-600 text-xs rounded-full">{% trans "Comment" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<small class="text-muted">{{ comment.created_at|date:"M d, Y P" }}</small>
|
<small class="text-gray-500 text-sm">{{ comment.created_at|date:"M d, Y P" }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="px-4 py-3">
|
||||||
<p class="card-text">{{ comment.content|safe }}</p>
|
<p class="text-gray-700 mb-0">{{ comment.content|safe }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="px-4 py-2 bg-gray-50 border-t border-gray-100">
|
||||||
{% if comment.author == user or user.is_staff %}
|
{% if comment.author == user or user.is_staff %}
|
||||||
<div class="btn-group btn-group-sm">
|
<div class="flex items-center gap-2">
|
||||||
<button type="button" class="btn btn-outline-primary"
|
<button type="button" class="inline-flex items-center gap-1 px-3 py-1.5 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white rounded-lg text-sm transition"
|
||||||
hx-get="{% url 'edit_meeting_comment' meeting.slug comment.id %}"
|
hx-get="{% url 'edit_meeting_comment' meeting.slug comment.id %}"
|
||||||
hx-target="#comment-section"
|
hx-target="#comment-section"
|
||||||
title="Edit Comment">
|
title="Edit Comment">
|
||||||
<i class="bi bi-pencil"></i>
|
<i data-lucide="pencil" class="w-4 h-4"></i>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-danger"
|
<button type="button" class="inline-flex items-center gap-1 px-3 py-1.5 border border-red-500 text-red-500 hover:bg-red-500 hover:text-white rounded-lg text-sm transition"
|
||||||
hx-get="{% url 'delete_meeting_comment' meeting.slug comment.id %}"
|
hx-get="{% url 'delete_meeting_comment' meeting.slug comment.id %}"
|
||||||
hx-target="#comment-section"
|
hx-target="#comment-section"
|
||||||
title="Delete Comment">
|
title="Delete Comment">
|
||||||
<i class="bi bi-trash"></i>
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p class="text-muted">{% trans "No comments yet. Be the first to comment!" %}</p>
|
<p class="text-gray-500 text-center py-4">{% trans "No comments yet. Be the first to comment!" %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
@ -1,20 +1,58 @@
|
|||||||
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
{% load i18n %}
|
||||||
<div id="copyToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
|
|
||||||
<div class="toast-header">
|
<!-- Toast Notification -->
|
||||||
<i class="fas fa-check-circle text-success me-2"></i>
|
<div id="copyToast" class="fixed bottom-4 right-4 z-50 transform translate-y-2 opacity-0 transition-all duration-300 pointer-events-none" role="alert" aria-live="assertive" aria-atomic="true">
|
||||||
<strong class="me-auto">{% trans "Success" %}</strong>
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden pointer-events-auto min-w-[320px]">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
<div class="flex items-center justify-between px-4 py-3 border-b border-gray-100 bg-gray-50">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<i data-lucide="check-circle" class="w-5 h-5 text-green-600"></i>
|
||||||
|
<strong class="text-gray-900">{% trans "Success" %}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="toast-body">
|
<button type="button" onclick="hideToast()" class="text-gray-400 hover:text-gray-600 transition">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-3 text-sm text-gray-700">
|
||||||
{% blocktrans with text=text %}Copied "{{ text }}" to clipboard!{% endblocktrans %}
|
{% blocktrans with text=text %}Copied "{{ text }}" to clipboard!{% endblocktrans %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Show toast notification
|
let toastTimeout;
|
||||||
|
|
||||||
|
function showToast() {
|
||||||
|
const toast = document.getElementById('copyToast');
|
||||||
|
if (toast) {
|
||||||
|
// Clear any existing timeout
|
||||||
|
if (toastTimeout) {
|
||||||
|
clearTimeout(toastTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show toast
|
||||||
|
toast.classList.remove('translate-y-2', 'opacity-0', 'pointer-events-none');
|
||||||
|
toast.classList.add('translate-y-0', 'opacity-100');
|
||||||
|
|
||||||
|
// Auto-hide after 3 seconds
|
||||||
|
toastTimeout = setTimeout(() => {
|
||||||
|
hideToast();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideToast() {
|
||||||
|
const toast = document.getElementById('copyToast');
|
||||||
|
if (toast) {
|
||||||
|
toast.classList.add('translate-y-2', 'opacity-0', 'pointer-events-none');
|
||||||
|
toast.classList.remove('translate-y-0', 'opacity-100');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show toast on page load
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const toast = new bootstrap.Toast(document.getElementById('copyToast'));
|
showToast();
|
||||||
toast.show();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize icons
|
||||||
|
lucide.createIcons();
|
||||||
</script>
|
</script>
|
||||||
@ -1,15 +1,18 @@
|
|||||||
<div class="card mt-4">
|
{% load i18n %}
|
||||||
<div class="card-header text-primary-theme">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
|
||||||
<h5 class="card-title mb-0">{% trans "Delete Comment" %}</h5>
|
<div class="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-red mb-0">{% trans "Delete Comment" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-4">
|
||||||
<p>{% trans "Are you sure you want to delete this comment by" %} <strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong>?</p>
|
<p class="text-gray-600 mb-2">{% trans "Are you sure you want to delete this comment by" %} <strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong>?</p>
|
||||||
<p><small>{{ comment.created_at|date:"F d, Y P" }}</small></p>
|
<p class="text-sm text-gray-500 mb-4"><small>{{ comment.created_at|date:"F d, Y P" }}</small></p>
|
||||||
<form method="post" action="{{ delete_url }}">
|
<form method="post" action="{{ delete_url }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-danger">{% trans "Yes, Delete" %}</button>
|
<button type="submit" class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white font-medium px-4 py-2 rounded-lg text-sm transition shadow-sm hover:shadow-md">
|
||||||
|
{% trans "Yes, Delete" %}
|
||||||
|
</button>
|
||||||
{% if 'HX-Request' in request.headers %}
|
{% if 'HX-Request' in request.headers %}
|
||||||
<button type="button" class="btn btn-secondary" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">{% trans "Cancel" %}</button>
|
<button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2 rounded-lg text-sm transition" hx-get="{% url 'meeting_details' meeting.slug %}" hx-select="#comment-section" hx-target="#comment-section">{% trans "Cancel" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,18 +1,22 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<!-- Delete Confirmation Modal -->
|
<!-- Delete Confirmation Modal -->
|
||||||
<div class="modal fade" id="meetingModal" tabindex="-1" aria-labelledby="meetingModalLabel" aria-hidden="true">
|
<div class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4" id="meetingModal" tabindex="-1" aria-labelledby="meetingModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="bg-white rounded-xl shadow-xl max-w-md w-full">
|
||||||
<div class="modal-content">
|
<div class="flex items-center justify-between p-4 border-b border-gray-200">
|
||||||
<div class="modal-header">
|
<h5 class="text-lg font-bold text-gray-900" id="meetingModalLabel">
|
||||||
<h5 class="modal-title" id="meetingModalLabel">
|
|
||||||
|
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition" aria-label="Close" onclick="document.getElementById('meetingModal').classList.add('hidden')">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="meetingModalBody" class="modal-body px-4 py-3">
|
<div id="meetingModalBody" class="p-4">
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
@ -4,35 +4,18 @@
|
|||||||
|
|
||||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="flex items-center justify-between p-4 border-b border-gray-200 bg-gray-50">
|
<div class="flex items-center justify-between p-4 border-b border-gray-200 bg-gray-50">
|
||||||
<h5 class="text-sm font-bold text-kaauh-blue flex items-center gap-2">
|
<h5 class="text-sm font-bold text-temple-red flex items-center gap-2">
|
||||||
<i data-lucide="folder-open" class="w-5 h-5"></i>
|
<i data-lucide="folder-open" class="w-5 h-5"></i>
|
||||||
{% trans "Documents" %}
|
{% trans "Documents" %}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-4 py-2 rounded-xl text-sm transition shadow-sm hover:shadow-md"
|
class="upload-modal-trigger inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2 rounded-xl text-sm transition shadow-sm hover:shadow-md"
|
||||||
onclick="document.getElementById('documentUploadModal').classList.remove('hidden')">
|
data-modal="documentUploadModal">
|
||||||
<i data-lucide="upload-cloud" class="w-4 h-4"></i>
|
<i data-lucide="upload-cloud" class="w-4 h-4"></i>
|
||||||
{% trans "Upload Document" %}
|
{% trans "Upload Document" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Document Upload Modal -->
|
|
||||||
<div id="documentUploadModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
|
|
||||||
<div class="bg-white rounded-2xl shadow-xl max-w-lg w-full">
|
|
||||||
<div class="flex items-center justify-between p-4 border-b border-gray-200">
|
|
||||||
<h5 class="text-lg font-bold text-gray-900">{% trans "Upload Document" %}</h5>
|
|
||||||
<button type="button"
|
|
||||||
onclick="document.getElementById('documentUploadModal').classList.add('hidden')"
|
|
||||||
class="text-gray-400 hover:text-gray-600 transition">
|
|
||||||
<i data-lucide="x" class="w-5 h-5"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="p-4">
|
|
||||||
{% include "forms/document_form.html" with slug=application.slug %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Documents List -->
|
<!-- Documents List -->
|
||||||
<div class="p-4" id="document-list-container">
|
<div class="p-4" id="document-list-container">
|
||||||
{% if documents %}
|
{% if documents %}
|
||||||
@ -41,7 +24,7 @@
|
|||||||
<div id="document-{{document.pk}}" class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 p-3 bg-gray-50 rounded-xl hover:bg-gray-100 transition">
|
<div id="document-{{document.pk}}" class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 p-3 bg-gray-50 rounded-xl hover:bg-gray-100 transition">
|
||||||
<div class="flex items-start gap-3 flex-1">
|
<div class="flex items-start gap-3 flex-1">
|
||||||
<div class="flex-shrink-0 mt-0.5">
|
<div class="flex-shrink-0 mt-0.5">
|
||||||
<i data-lucide="file" class="w-8 h-8 text-kaauh-blue"></i>
|
<i data-lucide="file" class="w-8 h-8 text-temple-red"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
<p class="font-semibold text-gray-900 mb-1">{{ document.get_document_type_display }}</p>
|
<p class="font-semibold text-gray-900 mb-1">{{ document.get_document_type_display }}</p>
|
||||||
@ -58,7 +41,7 @@
|
|||||||
|
|
||||||
<div class="flex items-center gap-2 flex-shrink-0">
|
<div class="flex items-center gap-2 flex-shrink-0">
|
||||||
<a href="{% url 'document_download' document.id %}"
|
<a href="{% url 'document_download' document.id %}"
|
||||||
class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-white border border-gray-200 text-gray-600 hover:text-kaauh-blue hover:border-kaauh-blue transition"
|
class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-white border border-gray-200 text-gray-600 hover:text-temple-red hover:border-temple-red transition"
|
||||||
title='{% trans "Download" %}'>
|
title='{% trans "Download" %}'>
|
||||||
<i data-lucide="download" class="w-4 h-4"></i>
|
<i data-lucide="download" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
@ -87,15 +70,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<!-- Document Upload Modal (moved outside the card structure to avoid z-index issues) -->
|
||||||
// Initialize Lucide icons
|
<div id="documentUploadModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-[60] hidden flex items-center justify-center p-4">
|
||||||
lucide.createIcons();
|
<div class="bg-white rounded-2xl shadow-xl max-w-lg w-full">
|
||||||
|
<div class="flex items-center justify-between p-4 border-b border-gray-200">
|
||||||
// Close modal on outside click
|
<h5 class="text-lg font-bold text-gray-900">{% trans "Upload Document" %}</h5>
|
||||||
document.addEventListener('click', function(e) {
|
<button type="button"
|
||||||
const modal = document.getElementById('documentUploadModal');
|
class="modal-close-btn text-gray-400 hover:text-gray-600 transition"
|
||||||
if (e.target === modal) {
|
data-modal="documentUploadModal">
|
||||||
modal.classList.add('hidden');
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
}
|
</button>
|
||||||
});
|
</div>
|
||||||
</script>
|
<div class="p-4">
|
||||||
|
{% include "forms/document_form.html" with slug=application.slug %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@ -4,345 +4,191 @@
|
|||||||
|
|
||||||
{% block title %}{% trans "Audit Dashboard" %}{% endblock %}
|
{% block title %}{% trans "Audit Dashboard" %}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
/* 1. Theme Variables (Teal Focus) */
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
:root {
|
|
||||||
--color-primary: #007a88; /* Main Teal */
|
|
||||||
--color-primary-dark: #004d55; /* Dark Teal for Headings, Login Status, and Strong Text */
|
|
||||||
|
|
||||||
/* Standard Dark Text (for max visibility) */
|
|
||||||
/* Muted text */
|
|
||||||
--color-text-on-dark: #f0f0f0; /* Light text for badges (guaranteed contrast) */
|
|
||||||
|
|
||||||
/* Adjusted Status Colors for contrast */
|
|
||||||
--color-success: #157347;
|
|
||||||
--color-danger: #bb2d3b;
|
|
||||||
--color-warning: #ffc107;
|
|
||||||
--color-info: #0dcaf0;
|
|
||||||
--color-light: #f8f9fa;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
/* 2. Layout, Header, and Summary */
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
.container-fluid {
|
|
||||||
background-color: var(--color-background-light);
|
|
||||||
}
|
|
||||||
.audit-card {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
||||||
min-height: 60vh;
|
|
||||||
}
|
|
||||||
.dashboard-header {
|
|
||||||
color: var(--color-primary-dark);
|
|
||||||
border-bottom: 1px solid #e9ecef;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.summary-alert {
|
|
||||||
border-color: var(--color-primary) !important;
|
|
||||||
background-color: var(--color-primary-light) !important;
|
|
||||||
}
|
|
||||||
.summary-alert h6, .summary-alert strong {
|
|
||||||
color: var(--color-primary-dark) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
/* 3. Tabs Styling */
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
.nav-tabs {
|
|
||||||
border-bottom: 2px solid #e9ecef;
|
|
||||||
background-color: #ffffff;
|
|
||||||
padding-top: 1rem;
|
|
||||||
border-radius: 0.75rem 0.75rem 0 0;
|
|
||||||
}
|
|
||||||
.nav-link-es {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
.nav-link-es.active {
|
|
||||||
color: var(--color-primary) !important;
|
|
||||||
font-weight: 600;
|
|
||||||
border-bottom: 3px solid var(--color-primary) !important;
|
|
||||||
}
|
|
||||||
.nav-link:hover:not(.active) {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
/* 4. Table and Text Contrast */
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
.table th {
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
.table td {
|
|
||||||
color: var(--color-text-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background-color: var(--color-light) !important;
|
|
||||||
color: var(--color-text-dark) !important;
|
|
||||||
}
|
|
||||||
code {
|
|
||||||
color: var(--color-text-dark) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
/* 5. badges VISIBILITY FIXES (Guaranteed Colors) */
|
|
||||||
/* ---------------------------------------------------- */
|
|
||||||
.badges {
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.4;
|
|
||||||
padding: 0.4em 0.6em;
|
|
||||||
min-width: 65px;
|
|
||||||
text-align: center;
|
|
||||||
text-transform: uppercase;
|
|
||||||
/* Ensure z-index doesn't cause issues if elements overlap */
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark badgess (CRUD Create/Delete & Login/Logout Type) - Use light text */
|
|
||||||
.badges-crud-create {
|
|
||||||
background-color: var(--color-success) !important;
|
|
||||||
color: var(--color-text-on-dark) !important;
|
|
||||||
}
|
|
||||||
.badges-crud-delete {
|
|
||||||
background-color: var(--color-danger) !important;
|
|
||||||
color: var(--color-text-on-dark) !important;
|
|
||||||
}
|
|
||||||
.badges-login-status {
|
|
||||||
background-color: var(--color-primary-dark) !important;
|
|
||||||
color: var(--color-text-on-dark) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Light badgess (CRUD Update & Request Method) - Use dark text */
|
|
||||||
.badges-crud-update {
|
|
||||||
background-color: var(--color-warning) !important;
|
|
||||||
color: var(--color-text-dark) !important;
|
|
||||||
}
|
|
||||||
.badges-request-method {
|
|
||||||
background-color: var(--color-info) !important;
|
|
||||||
color: var(--color-text-dark) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Pagination - Fully Teal Themed */
|
|
||||||
.pagination .page-item.active .page-link {
|
|
||||||
background-color: var(--color-primary) !important;
|
|
||||||
border-color: var(--color-primary) !important;
|
|
||||||
color: var(--color-text-on-dark) !important;
|
|
||||||
}
|
|
||||||
.pagination .page-link {
|
|
||||||
color: var(--color-primary) !important; /* FIX: Added !important here */
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
.pagination .page-item.disabled .page-link {
|
|
||||||
color: var(--color-text-secondary) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<!-- Breadcrumb -->
|
||||||
<div class="container-fluid pt-5 pb-5 px-lg-5" style="background-color: var(--color-background-light);">
|
<nav aria-label="breadcrumb" class="mb-4">
|
||||||
<nav aria-label="breadcrumb">
|
<ol class="flex items-center gap-2 text-sm">
|
||||||
<ol class="breadcrumb">
|
<li>
|
||||||
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li>
|
<a href="{% url 'settings' %}" class="text-gray-600 hover:text-temple-red transition">{% trans "Settings" %}</a>
|
||||||
<li class="breadcrumb-item active" aria-current="page" style="
|
</li>
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
<li class="text-gray-400">/</li>
|
||||||
font-weight: 600;
|
<li class="font-semibold text-temple-red">{% trans "System Activity" %}</li>
|
||||||
">{% trans "System Activity" %}</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
<h1 class="h3 fw-bold dashboard-header mb-4 px-3">
|
|
||||||
<i class="fas fa-shield-alt me-2" style="color: var(--color-primary);"></i>{% trans "System Activity Logs" %}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="alert summary-alert border-start border-5 p-3 mb-5 mx-3" role="alert">
|
<!-- Header -->
|
||||||
<h6 class="mb-1">{% trans "Viewing Logs" %}: <strong>{{ tab_title }}</strong></h6>
|
<div class="mb-4">
|
||||||
<p class="mb-0 small">
|
<h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
{% trans "Displaying" %}: **{{ logs.start_index }}-{{ logs.end_index }}** {% trans "of" %}
|
<i data-lucide="shield" class="w-6 h-6 text-temple-red"></i>
|
||||||
**{{ total_count }}** {% trans "total records." %}
|
{% trans "System Activity Logs" %}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Summary Alert -->
|
||||||
|
<div class="bg-temple-red/10 border-l-4 border-temple-red p-4 mb-6 rounded-r-lg">
|
||||||
|
<div class="text-sm font-semibold text-gray-800 mb-1">
|
||||||
|
{% trans "Viewing Logs" %}: <strong>{{ tab_title }}</strong>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-700">
|
||||||
|
{% trans "Displaying" %}: <strong>{{ logs.start_index }}-{{ logs.end_index }}</strong> {% trans "of" %}
|
||||||
|
<strong>{{ total_count }}</strong> {% trans "total records." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="audit-card mx-3">
|
<!-- Audit Card -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 min-h-[60vh]">
|
||||||
|
|
||||||
<ul class="nav nav-tabs px-3" id="auditTabs" role="tablist">
|
<!-- Tabs -->
|
||||||
<li class="nav-item" role="presentation">
|
<div class="flex border-b border-gray-200 px-3 pt-4 rounded-t-xl">
|
||||||
<a class="nav-link nav-link-es {% if active_tab == 'crud' %}active{% endif %}"
|
<a class="px-4 py-2 text-sm font-medium transition border-b-3 {% if active_tab == 'crud' %}text-temple-red border-temple-red{% else %}text-gray-600 border-transparent hover:text-temple-red hover:border-gray-300{% endif %}"
|
||||||
id="crud-tab" href="?tab=crud" aria-controls="crud">
|
href="?tab=crud">
|
||||||
<i class="fas fa-database me-2"></i>{% trans "Model Changes (CRUD)" %}
|
<i data-lucide="database" class="w-4 h-4 inline mr-2"></i>{% trans "Model Changes (CRUD)" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a class="px-4 py-2 text-sm font-medium transition border-b-3 {% if active_tab == 'login' %}text-temple-red border-temple-red{% else %}text-gray-600 border-transparent hover:text-temple-red hover:border-gray-300{% endif %}"
|
||||||
<li class="nav-item" role="presentation">
|
href="?tab=login">
|
||||||
<a class="nav-link nav-link-es {% if active_tab == 'login' %}active{% endif %}"
|
<i data-lucide="user-lock" class="w-4 h-4 inline mr-2"></i>{% trans "User Authentication" %}
|
||||||
id="login-tab" href="?tab=login" aria-controls="login">
|
|
||||||
<i class="fas fa-user-lock me-2"></i>{% trans "User Authentication" %}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a class="px-4 py-2 text-sm font-medium transition border-b-3 {% if active_tab == 'request' %}text-temple-red border-temple-red{% else %}text-gray-600 border-transparent hover:text-temple-red hover:border-gray-300{% endif %}"
|
||||||
<li class="nav-item" role="presentation">
|
href="?tab=request">
|
||||||
<a class="nav-link nav-link-es {% if active_tab == 'request' %}active{% endif %}"
|
<i data-lucide="globe" class="w-4 h-4 inline mr-2"></i>{% trans "HTTP Requests" %}
|
||||||
id="request-tab" href="?tab=request" aria-controls="request">
|
|
||||||
<i class="fas fa-globe me-2"></i>{% trans "HTTP Requests" %}
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="tab-content p-4" id="auditTabsContent">
|
<!-- Tab Content -->
|
||||||
|
<div class="p-4">
|
||||||
<div class="tab-pane fade show active" role="tabpanel">
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-sm">
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped table-hover small">
|
|
||||||
|
|
||||||
<thead>
|
<thead>
|
||||||
{% if active_tab == 'crud' %}
|
{% if active_tab == 'crud' %}
|
||||||
<tr>
|
<tr class="bg-gray-50">
|
||||||
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
|
||||||
<th scope="col" style="width: 15%;">{% trans "User" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
|
||||||
<th scope="col" style="width: 10%;">{% trans "Action" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Action" %}</th>
|
||||||
<th scope="col" style="width: 20%;">{% trans "Model" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Model" %}</th>
|
||||||
<th scope="col" style="width: 10%;">{% trans "Object PK" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Object PK" %}</th>
|
||||||
<th scope="col" style="width: 30%;">{% trans "Changes" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Changes" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% elif active_tab == 'login' %}
|
{% elif active_tab == 'login' %}
|
||||||
<tr>
|
<tr class="bg-gray-50">
|
||||||
<th scope="col" style="width: 20%;">{% trans "Date/Time" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
|
||||||
<th scope="col" style="width: 25%;">{% trans "User" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
|
||||||
<th scope="col" style="width: 15%;">{% trans "Type" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Type" %}</th>
|
||||||
<th scope="col" style="width: 10%;">{% trans "Status" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Status" %}</th>
|
||||||
<th scope="col" style="width: 30%;">{% trans "IP Address" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "IP Address" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% elif active_tab == 'request' %}
|
{% elif active_tab == 'request' %}
|
||||||
<tr>
|
<tr class="bg-gray-50">
|
||||||
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
|
||||||
<th scope="col" style="width: 15%;">{% trans "User" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
|
||||||
<th scope="col" style="width: 10%;">{% trans "Method" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Method" %}</th>
|
||||||
<th scope="col" style="width: 45%;">{% trans "Path" %}</th>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Path" %}</th>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody class="divide-y divide-gray-100">
|
||||||
{% for log in logs.object_list %}
|
{% for log in logs.object_list %}
|
||||||
{% if active_tab == 'crud' %}
|
{% if active_tab == 'crud' %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50">
|
||||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
<td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
<td>{{ log.user.email|default:"N/A" }}</td>
|
<td class="px-4 py-3 text-gray-900">{{ log.user.email|default:"N/A" }}</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
<span class="badge rounded-pill
|
{% if log.event_type == 1 %}
|
||||||
{% if log.event_type == 1 %}badges-crud-create
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i data-lucide="plus" class="w-3 h-3"></i>{% trans "CREATE" %}</span>
|
||||||
{% elif log.event_type == 2 %}badges-crud-update
|
{% elif log.event_type == 2 %}
|
||||||
{% else %}badges-crud-delete{% endif %}">
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-yellow-100 text-yellow-800 rounded-full text-xs font-medium"><i data-lucide="edit" class="w-3 h-3"></i>{% trans "UPDATE" %}</span>
|
||||||
{% if log.event_type == 1 %}<i class="fas fa-plus-circle me-1"></i>{% trans "CREATE" %}
|
{% else %}
|
||||||
{% elif log.event_type == 2 %}<i class="fas fa-edit me-1"></i>{% trans "UPDATE" %}
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium"><i data-lucide="trash-2" class="w-3 h-3"></i>{% trans "DELETE" %}</span>
|
||||||
{% else %}<i class="fas fa-trash-alt me-1"></i>{% trans "DELETE" %}{% endif %}
|
{% endif %}
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
<td><code style="color: var(--color-text-dark) !important;">{{ log.content_type.app_label }}.{{ log.content_type.model }}</code></td>
|
<td class="px-4 py-3"><code class="bg-gray-100 px-2 py-1 rounded text-xs">{{ log.content_type.app_label }}.{{ log.content_type.model }}</code></td>
|
||||||
<td>{{ log.object_id }}</td>
|
<td class="px-4 py-3 text-gray-900">{{ log.object_id }}</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
<pre class="p-2 m-0" style="font-size: 0.65rem; max-height: 80px; overflow-y: auto;">{{ log.changed_fields }}</pre>
|
<pre class="bg-gray-100 p-2 rounded text-xs overflow-x-auto max-h-20 overflow-y-auto">{{ log.changed_fields }}</pre>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% elif active_tab == 'login' %}
|
{% elif active_tab == 'login' %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50">
|
||||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
<td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
<td>
|
<td class="px-4 py-3 text-gray-900">
|
||||||
{% with user_obj=log.user %}
|
{% with user_obj=log.user %}
|
||||||
{% if user_obj %}
|
{% if user_obj %}
|
||||||
{{ user_obj.get_full_name|default:user_obj.username }}
|
{{ user_obj.get_full_name|default:user_obj.username }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-danger fw-bold">{{ log.username|default:"N/A" }}</span>
|
<span class="text-red-600 font-semibold">{{ log.username|default:"N/A" }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
<span class="badge rounded-pill badges-login-status">
|
<span class="inline-block px-2.5 py-1 bg-gray-700 text-white rounded-full text-xs font-medium">
|
||||||
{% if log.login_type == 0 %}{% trans "Login" %}
|
{% if log.login_type == 0 %}{% trans "Login" %}
|
||||||
{% elif log.login_type == 1 %}{% trans "Logout" %}
|
{% elif log.login_type == 1 %}{% trans "Logout" %}
|
||||||
{% else %}{% trans "Failed Login" %}{% endif %}
|
{% else %}{% trans "Failed Login" %}{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
{% if log.login_type == 2 %}
|
{% if log.login_type == 2 %}
|
||||||
<i class="fas fa-times-circle me-1" style="color: var(--color-danger);"></i>{% trans "Failed" %}
|
<span class="inline-flex items-center gap-1 text-red-600"><i data-lucide="x-circle" class="w-4 h-4"></i>{% trans "Failed" %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<i class="fas fa-check-circle me-1" style="color: var(--color-success);"></i>{% trans "Success" %}
|
<span class="inline-flex items-center gap-1 text-green-600"><i data-lucide="check-circle" class="w-4 h-4"></i>{% trans "Success" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ log.remote_ip|default:"Unknown" }}</td>
|
<td class="px-4 py-3 text-gray-900">{{ log.remote_ip|default:"Unknown" }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% elif active_tab == 'request' %}
|
{% elif active_tab == 'request' %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50">
|
||||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
<td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
<td>{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td>
|
<td class="px-4 py-3 text-gray-900">{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
<span class="badge rounded-pill badges-request-method">{{ log.method }}</span>
|
<span class="inline-block px-2.5 py-1 bg-gray-700 text-white rounded-full text-xs font-medium">{{ log.method }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td><code class="text-break small" style="color: var(--color-text-dark) !important;">{{ log.url}}</code></td>
|
<td class="px-4 py-3"><code class="bg-gray-100 px-2 py-1 rounded text-xs break-all">{{ log.url}}</code></td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr><td colspan="6" class="text-center text-muted py-5">
|
<tr>
|
||||||
<i class="fas fa-info-circle me-2"></i>{% trans "No logs found for this section or the database is empty." %}
|
<td colspan="6" class="px-4 py-12 text-center text-gray-500">
|
||||||
</td></tr>
|
<i data-lucide="info" class="w-5 h-5 inline mr-2"></i>{% trans "No logs found for this section or database is empty." %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
{% if logs.has_other_pages %}
|
{% if logs.has_other_pages %}
|
||||||
<nav aria-label="Audit Log Pagination" class="pt-3">
|
<div class="flex justify-end items-center gap-2 pt-4">
|
||||||
<ul class="pagination justify-content-end">
|
<a href="?tab={{ active_tab }}{% if logs.has_previous %}&page={{ logs.previous_page_number }}{% endif %}"
|
||||||
|
class="px-3 py-2 text-sm font-medium rounded-lg {% if logs.has_previous %}bg-white border border-gray-300 text-temple-red hover:bg-gray-50{% else %}bg-gray-100 text-gray-400 cursor-not-allowed{% endif %} transition">
|
||||||
<li class="page-item {% if not logs.has_previous %}disabled{% endif %}">
|
<i data-lucide="chevron-left" class="w-4 h-4 inline"></i>
|
||||||
<a class="page-link"
|
|
||||||
href="?tab={{ active_tab }}{% if logs.has_previous %}&page={{ logs.previous_page_number }}{% endif %}"
|
|
||||||
aria-label="Previous">
|
|
||||||
<span aria-hidden="true">«</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
|
|
||||||
{% for i in logs.paginator.page_range %}
|
{% for i in logs.paginator.page_range %}
|
||||||
{% comment %} Limiting pages displayed around the current page {% endcomment %}
|
|
||||||
{% if i > logs.number|add:'-3' and i < logs.number|add:'3' %}
|
{% if i > logs.number|add:'-3' and i < logs.number|add:'3' %}
|
||||||
<li class="page-item {% if logs.number == i %}active{% endif %}">
|
<a href="?tab={{ active_tab }}&page={{ i }}"
|
||||||
<a class="page-link"
|
class="px-3 py-2 text-sm font-medium rounded-lg {% if logs.number == i %}bg-temple-red text-white{% else %}bg-white border border-gray-300 text-temple-red hover:bg-gray-50{% endif %} transition">
|
||||||
href="?tab={{ active_tab }}&page={{ i }}">
|
|
||||||
{{ i }}
|
{{ i }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% elif i == logs.number|add:'-3' or i == logs.number|add:'3' %}
|
{% elif i == logs.number|add:'-3' or i == logs.number|add:'3' %}
|
||||||
<li class="page-item disabled"><span class="page-link">...</span></li>
|
<span class="px-3 py-2 text-sm text-gray-400">...</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
<li class="page-item {% if not logs.has_next %}disabled{% endif %}">
|
<a href="?tab={{ active_tab }}{% if logs.has_next %}&page={{ logs.next_page_number }}{% endif %}"
|
||||||
<a class="page-link"
|
class="px-3 py-2 text-sm font-medium rounded-lg {% if logs.has_next %}bg-white border border-gray-300 text-temple-red hover:bg-gray-50{% else %}bg-gray-100 text-gray-400 cursor-not-allowed{% endif %} transition">
|
||||||
href="?tab={{ active_tab }}{% if logs.has_next %}&page={{ logs.next_page_number }}{% endif %}"
|
<i data-lucide="chevron-right" class="w-4 h-4 inline"></i>
|
||||||
aria-label="Next">
|
|
||||||
<span aria-hidden="true">»</span>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</div>
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,23 +1,26 @@
|
|||||||
<div class="card mt-4">
|
{% load i18n %}
|
||||||
<div class="card-header text-primary-theme">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden mt-4">
|
||||||
<h5 class="card-title mb-0">{% trans "Edit Comment" %}</h5>
|
<div class="bg-gray-50 px-4 py-3 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-red mb-0">{% trans "Edit Comment" %}</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-4">
|
||||||
<form method="post" action="{% url 'edit_meeting_comment' meeting.slug comment.id %}">
|
<form method="post" action="{% url 'edit_meeting_comment' meeting.slug comment.id %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{{ form.content }}
|
{{ form.content }}
|
||||||
{% if form.content.errors %}
|
{% if form.content.errors %}
|
||||||
<div class="text-danger mt-1">
|
<div class="text-red-600 mt-1 text-sm">
|
||||||
{% for error in form.content.errors %}
|
{% for error in form.content.errors %}
|
||||||
<small>{{ error }}</small>
|
<small>{{ error }}</small>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn bg-primary btn-sm">{% trans "Update Comment" %}</button>
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2 rounded-lg text-sm transition shadow-sm hover:shadow-md">
|
||||||
|
{% trans "Update Comment" %}
|
||||||
|
</button>
|
||||||
{% if 'HX-Request' in request.headers %}
|
{% if 'HX-Request' in request.headers %}
|
||||||
<button type="button" class="btn btn-secondary btn-sm" hx-get="{% url 'meeting_details' meeting.slug %}" hx-target="#comment-section">{% trans "Cancel" %}</button>
|
<button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2 rounded-lg text-sm transition" hx-get="{% url 'meeting_details' meeting.slug %}" hx-target="#comment-section">{% trans "Cancel" %}</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,59 +1,60 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{{ form.media }}
|
{{ form.media }}
|
||||||
<div class="row">
|
|
||||||
|
|
||||||
<div class="container-fluid">
|
<div class="w-full max-w-2xl mx-auto">
|
||||||
|
<!-- Messages -->
|
||||||
<div class="col-12">
|
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<ul class="messages">
|
<div class="space-y-2 mb-4" role="alert" aria-live="polite">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
|
<div class="alert {% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-800{% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-800{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-800{% else %}bg-blue-50 border-blue-200 text-blue-800{% endif %} border rounded-lg px-4 py-3 flex items-start justify-between gap-2">
|
||||||
{{ message }}
|
<span class="flex-1 text-sm">{{ message }}</span>
|
||||||
</li>
|
<button type="button"
|
||||||
|
class="text-gray-400 hover:text-gray-600 p-1 shrink-0 touch-target"
|
||||||
|
onclick="this.parentElement.remove()"
|
||||||
|
aria-label="{% trans 'Dismiss' %}">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="card">
|
|
||||||
|
|
||||||
<div class="card-body">
|
<!-- Form Card -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
<div class="p-6">
|
||||||
<form hx-boost="true" method="post" id="email-compose-form" action="{% url 'compose_application_email' job.slug %}"
|
<form hx-boost="true" method="post" id="email-compose-form" action="{% url 'compose_application_email' job.slug %}"
|
||||||
hx-include="#application-form"
|
hx-include="#application-form"
|
||||||
|
|
||||||
hx-push-url="false"
|
hx-push-url="false"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-on::after-request="new bootstrap.Modal('#emailModal')).hide()">
|
hx-on::after-request="closeEmailModal()"
|
||||||
|
class="space-y-6">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<!-- Recipients Field -->
|
<!-- Recipients Field -->
|
||||||
<!-- Recipients Field -->
|
<div>
|
||||||
<div class="mb-3">
|
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
<label class="form-label fw-bold">
|
|
||||||
{% trans "To" %}
|
{% trans "To" %}
|
||||||
</label>
|
</label>
|
||||||
<div class="border rounded p-3 bg-light" style="max-height: 200px; overflow-y: auto;">
|
<div class="border-2 border-gray-300 rounded-lg p-4 bg-gray-50 max-h-48 overflow-y-auto">
|
||||||
|
<!-- Hidden inputs for all recipients -->
|
||||||
{# --- 1. DATA LAYER: Render Hidden Inputs for ALL recipients --- #}
|
|
||||||
{# This ensures the backend receives every selected user, not just the visible one #}
|
|
||||||
{% for choice in form.to %}
|
{% for choice in form.to %}
|
||||||
<input type="hidden" name="{{ form.to.name }}" value="{{ choice.data.value }}">
|
<input type="hidden" name="{{ form.to.name }}" value="{{ choice.data.value }}">
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{# --- 2. VISUAL LAYER: Show only the first one --- #}
|
<!-- Show first recipient only -->
|
||||||
{# We make it disabled so the user knows they can't deselect it here #}
|
|
||||||
{% for choice in form.to|slice:":1" %}
|
{% for choice in form.to|slice:":1" %}
|
||||||
<div class="form-check mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<input class="form-check-input" type="checkbox" checked disabled>
|
<div class="w-4 h-4 rounded border-2 border-temple-red bg-temple-red flex items-center justify-center">
|
||||||
<label class="form-check-label">
|
<i data-lucide="check" class="w-3 h-3 text-white"></i>
|
||||||
{{ choice.choice_label }}
|
</div>
|
||||||
</label>
|
<span class="text-sm text-gray-700">{{ choice.choice_label }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{# --- 3. SUMMARY: Show count of hidden recipients --- #}
|
<!-- Summary of remaining recipients -->
|
||||||
{% if form.to|length > 1 %}
|
{% if form.to|length > 1 %}
|
||||||
<div class="text-muted small mt-2">
|
<div class="flex items-center gap-2 text-gray-600 text-sm mt-2 pt-2 border-t border-gray-200">
|
||||||
<i class="fas fa-info-circle me-1"></i>
|
<i data-lucide="info" class="w-4 h-4 text-temple-red"></i>
|
||||||
{# Use simple math to show remaining count #}
|
|
||||||
{% with remaining=form.to|length|add:"-1" %}
|
{% with remaining=form.to|length|add:"-1" %}
|
||||||
{% blocktrans count total=remaining %}
|
{% blocktrans count total=remaining %}
|
||||||
And {{ total }} other recipient
|
And {{ total }} other recipient
|
||||||
@ -66,63 +67,77 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.to.errors %}
|
{% if form.to.errors %}
|
||||||
<div class="text-danger small mt-1">
|
<div class="text-red-600 text-sm mt-2">
|
||||||
{% for error in form.to.errors %}
|
{% for error in form.to.errors %}
|
||||||
<span>{{ error }}</span>
|
<span class="block">{{ error }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Subject Field -->
|
<!-- Subject Field -->
|
||||||
<div class="mb-3">
|
<div>
|
||||||
<label for="{{ form.subject.id_for_label }}" class="form-label fw-bold">
|
<label for="{{ form.subject.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
{% trans "Subject" %}
|
{% trans "Subject" %}
|
||||||
</label>
|
</label>
|
||||||
{{ form.subject }}
|
<div class="relative">
|
||||||
|
<input type="text"
|
||||||
|
name="{{ form.subject.name }}"
|
||||||
|
id="{{ form.subject.id_for_label }}"
|
||||||
|
class="w-full px-4 py-2.5 rounded-lg border-2 border-gray-300 focus:border-temple-red focus:ring-2 focus:ring-temple-red/20 focus:outline-none transition text-gray-900 placeholder-gray-400 text-sm"
|
||||||
|
placeholder="{% trans 'Enter email subject...' %}"
|
||||||
|
value="{{ form.subject.value|default:'' }}"
|
||||||
|
required>
|
||||||
{% if form.subject.errors %}
|
{% if form.subject.errors %}
|
||||||
<div class="text-danger small mt-1">
|
<div class="text-red-600 text-sm mt-2">
|
||||||
{% for error in form.subject.errors %}
|
{% for error in form.subject.errors %}
|
||||||
<span>{{ error }}</span>
|
<span class="block">{{ error }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Message Field -->
|
<!-- Message Field -->
|
||||||
<div class="mb-3">
|
<div>
|
||||||
<label for="{{ form.message.id_for_label }}" class="form-label fw-bold">
|
<label for="{{ form.message.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
{% trans "Message" %}
|
{% trans "Message" %}
|
||||||
</label>
|
</label>
|
||||||
{{ form.message }}
|
<div class="relative">
|
||||||
|
<textarea
|
||||||
|
name="{{ form.message.name }}"
|
||||||
|
id="{{ form.message.id_for_label }}"
|
||||||
|
rows="8"
|
||||||
|
class="w-full px-4 py-2.5 rounded-lg border-2 border-gray-300 focus:border-temple-red focus:ring-2 focus:ring-temple-red/20 focus:outline-none transition text-gray-900 placeholder-gray-400 text-sm resize-y"
|
||||||
|
placeholder="{% trans 'Write your message here...' %}"
|
||||||
|
required>{{ form.message.value|default:'' }}</textarea>
|
||||||
{% if form.message.errors %}
|
{% if form.message.errors %}
|
||||||
<div class="text-danger small mt-1">
|
<div class="text-red-600 text-sm mt-2">
|
||||||
{% for error in form.message.errors %}
|
{% for error in form.message.errors %}
|
||||||
<span>{{ error }}</span>
|
<span class="block">{{ error }}</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Form Actions -->
|
<!-- Form Actions -->
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 pt-4 border-t border-gray-200">
|
||||||
<div class="text-muted small">
|
<div class="flex items-center gap-2 text-gray-600 text-sm">
|
||||||
<i class="fas fa-info-circle me-1"></i>
|
<i data-lucide="info" class="w-4 h-4 text-temple-red"></i>
|
||||||
{% trans "Email will be sent to all selected recipients" %}
|
{% trans "Email will be sent to all selected recipients" %}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="flex items-center gap-3 w-full sm:w-auto">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="btn btn-secondary me-2"
|
class="flex-1 sm:flex-none flex items-center justify-center gap-2 px-6 py-2.5 rounded-lg border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 hover:border-gray-400 transition font-medium"
|
||||||
data-bs-dismiss="modal">
|
onclick="closeEmailModal()">
|
||||||
<i class="fas fa-times me-1"></i>
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</button>
|
</button>
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="btn btn-primary"
|
class="flex-1 sm:flex-none flex items-center justify-center gap-2 px-6 py-2.5 rounded-lg bg-temple-red text-white hover:bg-red-700 transition font-medium shadow-sm"
|
||||||
id="send-email-btn">
|
id="send-email-btn">
|
||||||
<i class="fas fa-paper-plane me-1"></i>
|
<i data-lucide="send" class="w-4 h-4"></i>
|
||||||
{% trans "Send Email" %}
|
{% trans "Send Email" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -130,88 +145,37 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Loading Overlay -->
|
<!-- Loading Overlay -->
|
||||||
<div id="email-loading-overlay" class="d-none">
|
<div id="email-loading-overlay" class="hidden fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center">
|
||||||
<div class="d-flex justify-content-center align-items-center" style="min-height: 200px;">
|
<div class="bg-white rounded-xl shadow-lg p-8 max-w-sm w-full mx-4">
|
||||||
<div class="text-center">
|
<div class="flex flex-col items-center gap-4">
|
||||||
<div class="spinner-border text-primary" role="status">
|
<div class="w-12 h-12 rounded-full border-4 border-temple-red border-t-transparent animate-spin" role="status">
|
||||||
<span class="visually-hidden">{% trans "Loading..." %}</span>
|
<span class="sr-only">{% trans "Loading..." %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2">
|
<div class="text-center">
|
||||||
{% trans "Sending email..." %}
|
<p class="text-lg font-semibold text-gray-800">{% trans "Sending email..." %}</p>
|
||||||
|
<p class="text-sm text-gray-600 mt-1">{% trans "Please wait..." %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Success/Error Messages Container -->
|
<!-- Success/Error Messages Container -->
|
||||||
<div id="email-messages-container"></div>
|
<div id="email-messages-container" class="fixed bottom-4 right-4 z-50 space-y-2 max-w-md"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
|
||||||
{{ form.media.css }}
|
|
||||||
.card {
|
|
||||||
border: none;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
border-radius: 8px 8px 0 0 !important;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus {
|
|
||||||
border-color: #00636e;
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0,99,110,0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background-color: #00636e;
|
|
||||||
border-color: #00636e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
background-color: #004a53;
|
|
||||||
border-color: #004a53;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-check-input:checked {
|
|
||||||
background-color: #00636e;
|
|
||||||
border-color: #00636e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border {
|
|
||||||
border-color: #dee2e6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-light {
|
|
||||||
background-color: #f8f9fa !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-danger {
|
|
||||||
color: #dc3545 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner-border {
|
|
||||||
width: 3rem;
|
|
||||||
height: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const form = document.getElementById('email-compose-form1');
|
const form = document.getElementById('email-compose-form');
|
||||||
const sendBtn = document.getElementById('send-email-btn1');
|
const sendBtn = document.getElementById('send-email-btn');
|
||||||
const loadingOverlay = document.getElementById('email-loading-overlay');
|
const loadingOverlay = document.getElementById('email-loading-overlay');
|
||||||
const messagesContainer = document.getElementById('email-messages-container');
|
const messagesContainer = document.getElementById('email-messages-container');
|
||||||
|
|
||||||
|
// Initialize Lucide icons
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener('submit', function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -219,11 +183,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Show loading state
|
// Show loading state
|
||||||
if (sendBtn) {
|
if (sendBtn) {
|
||||||
sendBtn.disabled = true;
|
sendBtn.disabled = true;
|
||||||
sendBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i> {% trans "Sending..." %}';
|
sendBtn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> {% trans "Sending..." %}';
|
||||||
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadingOverlay) {
|
if (loadingOverlay) {
|
||||||
loadingOverlay.classList.remove('d-none');
|
loadingOverlay.classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear previous messages
|
// Clear previous messages
|
||||||
@ -247,29 +212,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Hide loading state
|
// Hide loading state
|
||||||
if (sendBtn) {
|
if (sendBtn) {
|
||||||
sendBtn.disabled = false;
|
sendBtn.disabled = false;
|
||||||
sendBtn.innerHTML = '<i class="fas fa-paper-plane me-1"></i> {% trans "Send Email" %}';
|
sendBtn.innerHTML = '<i data-lucide="send" class="w-4 h-4"></i> {% trans "Send Email" %}';
|
||||||
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadingOverlay) {
|
if (loadingOverlay) {
|
||||||
loadingOverlay.classList.add('d-none');
|
loadingOverlay.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show result message
|
// Show result message
|
||||||
if (data.success) {
|
if (data.success || data.status === 'queued') {
|
||||||
showMessage(data.message || 'Email sent successfully!', 'success');
|
showMessage(data.message || 'Email enqueued successfully!', 'success');
|
||||||
|
|
||||||
// Close modal after a short delay
|
// Close modal after a short delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const modal = form.closest('.modal');
|
closeEmailModal();
|
||||||
if (modal) {
|
|
||||||
const bootstrapModal = bootstrap.Modal.getInstance(modal);
|
|
||||||
if (bootstrapModal) {
|
|
||||||
bootstrapModal.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 1500);
|
}, 1500);
|
||||||
} else {
|
} else {
|
||||||
showMessage(data.error || 'Failed to send email. Please try again.', 'danger');
|
showMessage(data.error || data.message || 'Failed to send email. Please try again.', 'danger');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@ -278,11 +238,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Hide loading state
|
// Hide loading state
|
||||||
if (sendBtn) {
|
if (sendBtn) {
|
||||||
sendBtn.disabled = false;
|
sendBtn.disabled = false;
|
||||||
sendBtn.innerHTML = '<i class="fas fa-paper-plane me-1"></i> {% trans "Send Email" %}';
|
sendBtn.innerHTML = '<i data-lucide="send" class="w-4 h-4"></i> {% trans "Send Email" %}';
|
||||||
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loadingOverlay) {
|
if (loadingOverlay) {
|
||||||
loadingOverlay.classList.add('d-none');
|
loadingOverlay.classList.add('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
showMessage('An error occurred while sending the email. Please try again.', 'danger');
|
showMessage('An error occurred while sending the email. Please try again.', 'danger');
|
||||||
@ -293,116 +254,62 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
function showMessage(message, type) {
|
function showMessage(message, type) {
|
||||||
if (!messagesContainer) return;
|
if (!messagesContainer) return;
|
||||||
|
|
||||||
const alertClass = type === 'success' ? 'alert-success' : 'alert-danger';
|
const bgColor = type === 'success' ? 'bg-green-50 border-green-200 text-green-800' : 'bg-red-50 border-red-200 text-red-800';
|
||||||
const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-triangle';
|
const icon = type === 'success' ? 'check-circle' : 'alert-triangle';
|
||||||
|
|
||||||
const messageHtml = `
|
const messageDiv = document.createElement('div');
|
||||||
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
|
messageDiv.className = `alert ${bgColor} border rounded-lg px-4 py-3 flex items-start gap-2 shadow-lg transform transition-all duration-300 translate-x-full`;
|
||||||
<i class="fas ${icon} me-2"></i>
|
messageDiv.innerHTML = `
|
||||||
${message}
|
<i data-lucide="${icon}" class="w-5 h-5 shrink-0 mt-0.5"></i>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
<span class="flex-1 text-sm">${message}</span>
|
||||||
</div>
|
<button type="button" class="text-gray-400 hover:text-gray-600 p-1 shrink-0" onclick="this.parentElement.remove()">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
messagesContainer.innerHTML = messageHtml;
|
messagesContainer.appendChild(messageDiv);
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
|
// Animate in
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
messageDiv.classList.remove('translate-x-full');
|
||||||
|
});
|
||||||
|
|
||||||
// Auto-hide success messages after 5 seconds
|
// Auto-hide success messages after 5 seconds
|
||||||
if (type === 'success') {
|
if (type === 'success') {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const alert = messagesContainer.querySelector('.alert');
|
messageDiv.style.opacity = '0';
|
||||||
if (alert) {
|
messageDiv.style.transform = 'translateX(100%)';
|
||||||
const bsAlert = new bootstrap.Alert(alert);
|
setTimeout(() => messageDiv.remove(), 300);
|
||||||
bsAlert.close();
|
|
||||||
}
|
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form validation
|
|
||||||
function validateForm() {
|
|
||||||
let isValid = true;
|
|
||||||
const subject = form.querySelector('#{{ form.subject.id_for_label }}');
|
|
||||||
const message = form.querySelector('#{{ form.message.id_for_label }}');
|
|
||||||
const recipients = form.querySelectorAll('input[name="{{ form.recipients.name }}"]:checked');
|
|
||||||
|
|
||||||
// Clear previous validation states
|
|
||||||
form.querySelectorAll('.is-invalid').forEach(field => {
|
|
||||||
field.classList.remove('is-invalid');
|
|
||||||
});
|
|
||||||
form.querySelectorAll('.invalid-feedback').forEach(feedback => {
|
|
||||||
feedback.remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Validate subject
|
|
||||||
if (!subject || !subject.value.trim()) {
|
|
||||||
showFieldError(subject, 'Subject is required');
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate message
|
|
||||||
if (!message || !message.value.trim()) {
|
|
||||||
showFieldError(message, 'Message is required');
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate recipients
|
|
||||||
if (recipients.length === 0) {
|
|
||||||
const recipientsContainer = form.querySelector('.border.rounded.p-3.bg-light');
|
|
||||||
if (recipientsContainer) {
|
|
||||||
recipientsContainer.classList.add('border-danger');
|
|
||||||
showFieldError(recipientsContainer, 'Please select at least one recipient');
|
|
||||||
}
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showFieldError(field, message) {
|
|
||||||
if (!field) return;
|
|
||||||
|
|
||||||
field.classList.add('is-invalid');
|
|
||||||
|
|
||||||
const feedback = document.createElement('div');
|
|
||||||
feedback.className = 'invalid-feedback';
|
|
||||||
feedback.textContent = message;
|
|
||||||
|
|
||||||
if (field.classList.contains('border')) {
|
|
||||||
// For container elements (like recipients)
|
|
||||||
field.parentNode.appendChild(feedback);
|
|
||||||
} else {
|
|
||||||
// For form fields
|
|
||||||
field.parentNode.appendChild(feedback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Character counter for message field
|
// Character counter for message field
|
||||||
function setupCharacterCounter() {
|
function setupCharacterCounter() {
|
||||||
const messageField = form.querySelector('#{{ form.message.id_for_label }}');
|
const messageField = form.querySelector('#{{ form.message.id_for_label }}');
|
||||||
if (!messageField) return;
|
if (!messageField) return;
|
||||||
|
|
||||||
const counter = document.createElement('div');
|
const counter = document.createElement('div');
|
||||||
counter.className = 'text-muted small mt-1';
|
counter.className = 'text-gray-500 text-sm mt-2 text-right';
|
||||||
counter.id = 'message-counter';
|
counter.id = 'message-counter';
|
||||||
|
|
||||||
messageField.parentNode.appendChild(counter);
|
messageField.parentNode.appendChild(counter);
|
||||||
|
|
||||||
function updateCounter() {
|
function updateCounter() {
|
||||||
const length = messageField.value.length;
|
const length = messageField.value.length;
|
||||||
const maxLength = 5000; // Adjust as needed
|
const maxLength = 5000;
|
||||||
counter.textContent = `${length} / ${maxLength} characters`;
|
counter.textContent = `${length} / ${maxLength} characters`;
|
||||||
|
|
||||||
if (length > maxLength * 0.9) {
|
if (length > maxLength * 0.9) {
|
||||||
counter.classList.add('text-warning');
|
counter.className = 'text-yellow-600 text-sm mt-2 text-right';
|
||||||
counter.classList.remove('text-muted');
|
|
||||||
} else {
|
} else {
|
||||||
counter.classList.remove('text-warning');
|
counter.className = 'text-gray-500 text-sm mt-2 text-right';
|
||||||
counter.classList.add('text-muted');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
messageField.addEventListener('input', updateCounter);
|
messageField.addEventListener('input', updateCounter);
|
||||||
updateCounter(); // Initial count
|
updateCounter();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-save functionality
|
// Auto-save functionality
|
||||||
@ -417,17 +324,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const draftData = {
|
const draftData = {
|
||||||
subject: subject.value,
|
subject: subject.value,
|
||||||
message: message.value,
|
message: message.value,
|
||||||
recipients: Array.from(form.querySelectorAll('input[name="{{ form.recipients.name }}"]:checked')).map(cb => cb.value),
|
timestamp: new Date().toISOString()
|
||||||
include_application_info: form.querySelector('#{{ form.include_application_info.id_for_label }}').checked,
|
|
||||||
include_meeting_details: form.querySelector('#{{ form.include_meeting_details.id_for_label }}').checked
|
|
||||||
};
|
};
|
||||||
|
|
||||||
localStorage.setItem('email_draft_' + window.location.pathname, JSON.stringify(draftData));
|
localStorage.setItem('email_draft_' + window.location.pathname, JSON.stringify(draftData));
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoSave() {
|
function autoSave() {
|
||||||
clearTimeout(autoSaveTimer);
|
clearTimeout(autoSaveTimer);
|
||||||
autoSaveTimer = setTimeout(saveDraft, 2000); // Save after 2 seconds of inactivity
|
autoSaveTimer = setTimeout(saveDraft, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
[subject, message].forEach(field => {
|
[subject, message].forEach(field => {
|
||||||
@ -450,21 +354,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (subject && draft.subject) subject.value = draft.subject;
|
if (subject && draft.subject) subject.value = draft.subject;
|
||||||
if (message && draft.message) message.value = draft.message;
|
if (message && draft.message) message.value = draft.message;
|
||||||
|
|
||||||
// Restore recipients
|
|
||||||
if (draft.recipients) {
|
|
||||||
form.querySelectorAll('input[name="{{ form.recipients.name }}"]').forEach(cb => {
|
|
||||||
cb.checked = draft.recipients.includes(cb.value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore checkboxes
|
|
||||||
if (draft.include_application_info) {
|
|
||||||
form.querySelector('#{{ form.include_application_info.id_for_label }}').checked = draft.include_application_info;
|
|
||||||
}
|
|
||||||
if (draft.include_meeting_details) {
|
|
||||||
form.querySelector('#{{ form.include_meeting_details.id_for_label }}').checked = draft.include_meeting_details;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show draft restored notification
|
// Show draft restored notification
|
||||||
showMessage('Draft restored from local storage', 'success');
|
showMessage('Draft restored from local storage', 'success');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -484,16 +373,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
setTimeout(loadDraft, 100);
|
setTimeout(loadDraft, 100);
|
||||||
|
|
||||||
// Clear draft on successful submission
|
// Clear draft on successful submission
|
||||||
const originalSubmitHandler = form.onsubmit;
|
form.addEventListener('submit', function() {
|
||||||
form.addEventListener('submit', function(e) {
|
|
||||||
const isValid = validateForm();
|
|
||||||
if (!isValid) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear draft on successful submission
|
|
||||||
setTimeout(clearDraft, 2000);
|
setTimeout(clearDraft, 2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -509,16 +389,19 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Escape to cancel/close modal
|
// Escape to cancel/close modal
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
const modal = form.closest('.modal');
|
closeEmailModal();
|
||||||
if (modal) {
|
|
||||||
const bootstrapModal = bootstrap.Modal.getInstance(modal);
|
|
||||||
if (bootstrapModal) {
|
|
||||||
bootstrapModal.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Email compose form initialized');
|
console.log('Email compose form initialized');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Global function to close email modal
|
||||||
|
function closeEmailModal() {
|
||||||
|
const modal = document.querySelector('[id^="emailModal"]');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -1,18 +1,18 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
<form action="{% url 'set_language' %}" method="post" class="inline">{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ next_url }}">
|
<input name="next" type="hidden" value="{{ next_url }}">
|
||||||
<button name="language" value="en" class="dropdown-item {% if LANGUAGE_CODE == 'en' %}active bg-light-subtle{% endif %}" type="submit">
|
<button name="language" value="en" class="w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100 hover:text-temple-red {% if LANGUAGE_CODE == 'en' %}bg-temple-red text-white hover:bg-temple-dark{% endif %} transition" type="submit">
|
||||||
<span class="me-2">🇺🇸</span> English
|
<span class="mr-2">🇺🇸</span> English
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
<form action="{% url 'set_language' %}" method="post" class="inline">{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ next_url }}">
|
<input name="next" type="hidden" value="{{ next_url }}">
|
||||||
<button name="language" value="ar" class="dropdown-item {% if LANGUAGE_CODE == 'ar' %}active bg-light-subtle{% endif %}" type="submit">
|
<button name="language" value="ar" class="w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100 hover:text-temple-red {% if LANGUAGE_CODE == 'ar' %}bg-temple-red text-white hover:bg-temple-dark{% endif %} transition" type="submit">
|
||||||
<span class="me-2">🇸🇦</span> العربية
|
<span class="mr-2">🇸🇦</span> العربية
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
@ -1,44 +1,45 @@
|
|||||||
|
{% load i18n %}
|
||||||
<!-- This snippet is loaded by HTMX into #meetingModalBody -->
|
<!-- This snippet is loaded by HTMX into #meetingModalBody -->
|
||||||
<form id="meetingForm" method="post" action="{{ action_url }}?_target=modal" data-bs-theme="light">
|
<form id="meetingForm" method="post" action="{{ action_url }}?_target=modal">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="candidate_pk" value="{{ candidate.pk }}">
|
<input type="hidden" name="candidate_pk" value="{{ candidate.pk }}">
|
||||||
{% if scheduled_interview %}
|
{% if scheduled_interview %}
|
||||||
<input type="hidden" name="interview_pk" value="{{ scheduled_interview.pk }}">
|
<input type="hidden" name="interview_pk" value="{{ scheduled_interview.pk }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label for="id_topic" class="form-label">{% trans "Topic" %}</label>
|
<label for="id_topic" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Topic" %}</label>
|
||||||
<input type="text" class="form-control" id="id_topic" name="topic" value="{{ initial_data.topic|default:'' }}" required>
|
<input type="text" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_topic" name="topic" value="{{ initial_data.topic|default:'' }}" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label for="id_start_time" class="form-label">{% trans "Start Time and Date" %}</label>
|
<label for="id_start_time" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time and Date" %}</label>
|
||||||
<input type="datetime-local" class="form-control" id="id_start_time" name="start_time" value="{{ initial_data.start_time|default:'' }}" required>
|
<input type="datetime-local" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_start_time" name="start_time" value="{{ initial_data.start_time|default:'' }}" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label for="id_duration" class="form-label">{% trans "Duration (minutes)" %}</label>
|
<label for="id_duration" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Duration (minutes)" %}</label>
|
||||||
<input type="number" class="form-control" id="id_duration" name="duration" value="{{ initial_data.duration|default:60 }}" min="15" step="15" required>
|
<input type="number" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_duration" name="duration" value="{{ initial_data.duration|default:60 }}" min="15" step="15" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="meetingDetails" class="alert alert-info" style="display: none;">
|
<div id="meetingDetails" class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4" style="display: none;">
|
||||||
<strong>{% trans "Meeting Details (will appear after scheduling):" %}</strong>
|
<strong class="block text-blue-900 mb-2">{% trans "Meeting Details (will appear after scheduling):" %}</strong>
|
||||||
<p><strong>{% trans "Join URL:" %}</strong> <a id="joinUrlDisplay" href="#" target="_blank"></a></p>
|
<p class="mb-1"><strong>{% trans "Join URL:" %}</strong> <a id="joinUrlDisplay" href="#" target="_blank" class="text-temple-red hover:underline"></a></p>
|
||||||
<p><strong>{% trans "Meeting ID:" %}</strong> <span id="meetingIdDisplay"></span></p>
|
<p class="mb-0"><strong>{% trans "Meeting ID:" %}</strong> <span id="meetingIdDisplay"></span></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="successMessage" class="alert alert-success" style="display: none;">
|
<div id="successMessage" class="bg-green-50 border border-green-200 rounded-lg p-4 mb-4" style="display: none;">
|
||||||
<span id="successText"></span>
|
<span id="successText" class="block text-green-900"></span>
|
||||||
<small><a id="joinLinkSuccess" href="#" target="_blank" style="color: inherit; text-decoration: underline;">{% trans "Click here to join meeting" %}</a></small>
|
<small><a id="joinLinkSuccess" href="#" target="_blank" class="text-green-700 hover:underline">{% trans "Click here to join meeting" %}</a></small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="errorMessage" class="alert alert-danger" style="display: none;">
|
<div id="errorMessage" class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4" style="display: none;">
|
||||||
<span id="errorText"></span>
|
<span id="errorText" class="block text-red-900"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-end gap-2 mt-4">
|
<div class="flex items-center justify-end gap-3 mt-4">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-dismiss="modal">{% trans "Cancel" %}</button>
|
<button type="button" class="inline-flex items-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2.5 rounded-lg text-sm transition" onclick="document.getElementById('meetingModal').classList.add('hidden')">{% trans "Cancel" %}</button>
|
||||||
<button type="submit" class="btn btn-primary" id="scheduleBtn">
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-4 py-2.5 rounded-lg text-sm transition shadow-sm hover:shadow-md" id="scheduleBtn">
|
||||||
{% if scheduled_interview %}{% trans "Reschedule Meeting" %}{% else %}{% trans "Schedule Meeting" %}{% endif %}
|
{% if scheduled_interview %}{% trans "Reschedule Meeting" %}{% else %}{% trans "Schedule Meeting" %}{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -57,20 +58,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const joinLinkSuccess = document.getElementById('joinLinkSuccess');
|
const joinLinkSuccess = document.getElementById('joinLinkSuccess');
|
||||||
const errorMessageDiv = document.getElementById('errorMessage');
|
const errorMessageDiv = document.getElementById('errorMessage');
|
||||||
const errorText = document.getElementById('errorText');
|
const errorText = document.getElementById('errorText');
|
||||||
const modalElement = document.getElementById('meetingModal'); // This should be on the parent page
|
|
||||||
const modalTitle = document.querySelector('#meetingModal .modal-title'); // Parent page element
|
|
||||||
|
|
||||||
// Update modal title based on data attributes from the triggering button (if available)
|
|
||||||
// This is a fallback, ideally the parent page JS updates the title before fetching.
|
|
||||||
// Or, the view context could set a variable for the title.
|
|
||||||
// For simplicity, we'll assume parent page JS or rely on initial context.
|
|
||||||
const modalTitleText = modalTitle.getAttribute('data-current-title') || "{% trans 'Schedule Interview' %}";
|
|
||||||
if (modalTitle) {
|
|
||||||
modalTitle.textContent = modalTitleText;
|
|
||||||
}
|
|
||||||
const submitBtnText = scheduleBtn.getAttribute('data-current-submit-text') || "{% trans 'Schedule Meeting' %}";
|
|
||||||
scheduleBtn.textContent = submitBtnText;
|
|
||||||
|
|
||||||
|
const submitBtnText = scheduleBtn.textContent || "{% trans 'Schedule Meeting' %}";
|
||||||
|
|
||||||
form.addEventListener('submit', function(event) {
|
form.addEventListener('submit', function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@ -80,12 +69,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
errorMessageDiv.style.display = 'none';
|
errorMessageDiv.style.display = 'none';
|
||||||
|
|
||||||
scheduleBtn.disabled = true;
|
scheduleBtn.disabled = true;
|
||||||
scheduleBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span> {% trans "Processing..." %}';
|
scheduleBtn.innerHTML = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg> {% trans "Processing..." %}';
|
||||||
|
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
// Check if the target is modal, if so, we might want to close it on success
|
|
||||||
const isModalTarget = new URLSearchParams(window.location.search).get('_target') === 'modal';
|
const isModalTarget = new URLSearchParams(window.location.search).get('_target') === 'modal';
|
||||||
const url = form.action.replace(/\?.*$/, ""); // Remove any existing query params like _target
|
const url = form.action.replace(/\?.*$/, "");
|
||||||
|
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -95,10 +83,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(response => response.json()) // Always expect JSON for HTMX success/error
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
scheduleBtn.disabled = false;
|
scheduleBtn.disabled = false;
|
||||||
scheduleBtn.innerHTML = submitBtnText; // Reset to original text
|
scheduleBtn.textContent = submitBtnText;
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
successText.textContent = data.message;
|
successText.textContent = data.message;
|
||||||
@ -107,24 +95,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
joinUrlDisplay.textContent = data.join_url;
|
joinUrlDisplay.textContent = data.join_url;
|
||||||
joinUrlDisplay.href = data.join_url;
|
joinUrlDisplay.href = data.join_url;
|
||||||
joinLinkSuccess.href = data.join_url;
|
joinLinkSuccess.href = data.join_url;
|
||||||
meetingDetailsDiv.style.display = 'block'; // Keep meeting details shown
|
meetingDetailsDiv.style.display = 'block';
|
||||||
}
|
}
|
||||||
if (data.meeting_id) {
|
if (data.meeting_id) {
|
||||||
meetingIdDisplay.textContent = data.meeting_id;
|
meetingIdDisplay.textContent = data.meeting_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isModalTarget && modalElement) {
|
if (isModalTarget) {
|
||||||
const bsModal = bootstrap.Modal.getInstance(modalElement);
|
const modalElement = document.getElementById('meetingModal');
|
||||||
if (bsModal) bsModal.hide();
|
if (modalElement) {
|
||||||
|
modalElement.classList.add('hidden');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Optionally, trigger an event on the parent page to update its list
|
|
||||||
if (window.parent && window.parent.dispatchEvent) {
|
if (window.parent && window.parent.dispatchEvent) {
|
||||||
window.parent.dispatchEvent(new CustomEvent('meetingUpdated', { detail: data }));
|
window.parent.dispatchEvent(new CustomEvent('meetingUpdated', { detail: data }));
|
||||||
} else {
|
|
||||||
// Fallback: reload the page if it's not in an iframe or parent dispatch is not available
|
|
||||||
// window.location.reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
errorText.textContent = data.error || '{% trans "An unknown error occurred." %}';
|
errorText.textContent = data.error || '{% trans "An unknown error occurred." %}';
|
||||||
errorMessageDiv.style.display = 'block';
|
errorMessageDiv.style.display = 'block';
|
||||||
@ -133,13 +118,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
scheduleBtn.disabled = false;
|
scheduleBtn.disabled = false;
|
||||||
scheduleBtn.innerHTML = submitBtnText;
|
scheduleBtn.textContent = submitBtnText;
|
||||||
errorText.textContent = '{% trans "An error occurred while processing your request." %}';
|
errorText.textContent = '{% trans "An error occurred while processing your request." %}';
|
||||||
errorMessageDiv.style.display = 'block';
|
errorMessageDiv.style.display = 'block';
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Repopulate if initial_data was passed (for rescheduling)
|
|
||||||
{% if initial_data %}
|
{% if initial_data %}
|
||||||
document.getElementById('id_topic').value = '{{ initial_data.topic }}';
|
document.getElementById('id_topic').value = '{{ initial_data.topic }}';
|
||||||
document.getElementById('id_start_time').value = '{{ initial_data.start_time }}';
|
document.getElementById('id_start_time').value = '{{ initial_data.start_time }}';
|
||||||
|
|||||||
@ -1,63 +1,60 @@
|
|||||||
<div class="container mt-4">
|
{% load i18n %}
|
||||||
<h1>Schedule Interviews for {{ job.title }}</h1>
|
<div class="container mx-auto mt-8 px-4">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 mb-6">{% trans "Schedule Interviews for" %} {{ job.title }}</h1>
|
||||||
|
|
||||||
<div class="card mt-4">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<form method="post" id="schedule-form">
|
<form method="post" id="schedule-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
<div class="col-md-6">
|
<!-- Left Column - Candidates -->
|
||||||
<h5>{% trans "Select Candidates" %}</h5>
|
<div>
|
||||||
<div class="form-group">
|
<h5 class="text-lg font-semibold text-gray-900 mb-4">{% trans "Select Candidates" %}</h5>
|
||||||
|
<div class="space-y-2">
|
||||||
{{ form.candidates }}
|
{{ form.candidates }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<!-- Right Column - Schedule Details -->
|
||||||
<h5>{% trans "Schedule Details" %}</h5>
|
<div>
|
||||||
|
<h5 class="text-lg font-semibold text-gray-900 mb-4">{% trans "Schedule Details" %}</h5>
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
<div class="space-y-4">
|
||||||
<label for="{{ form.start_date.id_for_label }}">{% trans "Start Date" %}</label>
|
<div>
|
||||||
|
<label for="{{ form.start_date.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Date" %}</label>
|
||||||
{{ form.start_date }}
|
{{ form.start_date }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
<div>
|
||||||
<label for="{{ form.end_date.id_for_label }}">{% trans "End Date" %}</label>
|
<label for="{{ form.end_date.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Date" %}</label>
|
||||||
{{ form.end_date }}
|
{{ form.end_date }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
<div>
|
||||||
<label>{% trans "Working Days" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "Working Days" %}</label>
|
||||||
{{ form.working_days }}
|
{{ form.working_days }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<div class="form-group mb-3">
|
<label for="{{ form.start_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time" %}</label>
|
||||||
<label for="{{ form.start_time.id_for_label }}">{% trans "Start Time" %}</label>
|
|
||||||
{{ form.start_time }}
|
{{ form.start_time }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<div class="form-group mb-3">
|
<label for="{{ form.end_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
|
||||||
<label for="{{ form.end_time.id_for_label }}">{% trans "End Time" %}</label>
|
|
||||||
{{ form.end_time }}
|
{{ form.end_time }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<div class="form-group mb-3">
|
<label for="{{ form.interview_duration.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Interview Duration (minutes)" %}</label>
|
||||||
<label for="{{ form.interview_duration.id_for_label }}">{% trans "Interview Duration (minutes)" %}</label>
|
|
||||||
{{ form.interview_duration }}
|
{{ form.interview_duration }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<div class="form-group mb-3">
|
<label for="{{ form.buffer_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Buffer Time (minutes)" %}</label>
|
||||||
<label for="{{ form.buffer_time.id_for_label }}">{% trans "Buffer Time (minutes)" %}</label>
|
|
||||||
{{ form.buffer_time }}
|
{{ form.buffer_time }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -65,36 +62,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row mt-4">
|
<!-- Break Times -->
|
||||||
<div class="col-12">
|
<div class="mt-8">
|
||||||
<h5>Break Times</h5>
|
<h5 class="text-lg font-semibold text-gray-900 mb-4">{% trans "Break Times" %}</h5>
|
||||||
<div id="break-times-container">
|
<div id="break-times-container" class="space-y-4">
|
||||||
{{ break_formset.management_form }}
|
{{ break_formset.management_form }}
|
||||||
{% for form in break_formset %}
|
{% for form in break_formset %}
|
||||||
<div class="break-time-form row mb-2">
|
<div class="break-time-form grid grid-cols-12 gap-4 items-start">
|
||||||
<div class="col-md-5">
|
<div class="col-span-5">
|
||||||
<label>{% trans "Start Time" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time" %}</label>
|
||||||
{{ form.start_time }}
|
{{ form.start_time }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col-span-5">
|
||||||
<label>{% trans "End Time" %}</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
|
||||||
{{ form.end_time }}
|
{{ form.end_time }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-span-2 flex items-end">
|
||||||
<label> </label><br>
|
|
||||||
{{ form.DELETE }}
|
{{ form.DELETE }}
|
||||||
<button type="button" class="btn btn-danger btn-sm remove-break">{% trans "Remove" %}</button>
|
<button type="button" class="remove-break w-full bg-red-500 hover:bg-red-600 text-white text-sm font-medium px-4 py-2 rounded-lg transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="trash-2" class="w-4 h-4 inline mr-1"></i>{% trans "Remove" %}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<button type="button" id="add-break" class="btn btn-secondary btn-sm mt-2">{% trans "Add Break" %}</button>
|
<button type="button" id="add-break" class="mt-4 bg-gray-200 hover:bg-gray-300 text-gray-700 text-sm font-medium px-4 py-2 rounded-lg transition">
|
||||||
</div>
|
<i data-lucide="plus" class="w-4 h-4 inline mr-1"></i>{% trans "Add Break" %}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4">
|
<!-- Action Buttons -->
|
||||||
<button type="submit" class="btn btn-primary">{% trans "Preview Schedule" %}</button>
|
<div class="mt-8 flex gap-3">
|
||||||
<a href="{% url 'job_detail' slug=job.slug %}" class="btn btn-secondary">{% trans "Cancel" %}</a>
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-dark text-white font-medium px-6 py-2.5 rounded-lg text-sm transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i>{% trans "Preview Schedule" %}
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'job_detail' slug=job.slug %}" class="inline-flex items-center gap-2 bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium px-6 py-2.5 rounded-lg text-sm transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>{% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -110,19 +114,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
addBreakBtn.addEventListener('click', function() {
|
addBreakBtn.addEventListener('click', function() {
|
||||||
const formCount = parseInt(totalFormsInput.value);
|
const formCount = parseInt(totalFormsInput.value);
|
||||||
const newFormHtml = `
|
const newFormHtml = `
|
||||||
<div class="break-time-form row mb-2">
|
<div class="break-time-form grid grid-cols-12 gap-4 items-start">
|
||||||
<div class="col-md-5">
|
<div class="col-span-5">
|
||||||
<label>Start Time</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time" %}</label>
|
||||||
<input type="time" name="breaks-${formCount}-start_time" class="form-control" id="id_breaks-${formCount}-start_time">
|
<input type="time" name="breaks-${formCount}-start_time" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_breaks-${formCount}-start_time">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
<div class="col-span-5">
|
||||||
<label>End Time</label>
|
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
|
||||||
<input type="time" name="breaks-${formCount}-end_time" class="form-control" id="id_breaks-${formCount}-end_time">
|
<input type="time" name="breaks-${formCount}-end_time" class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition" id="id_breaks-${formCount}-end_time">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-span-2 flex items-end">
|
||||||
<label> </label><br>
|
|
||||||
<input type="checkbox" name="breaks-${formCount}-DELETE" id="id_breaks-${formCount}-DELETE" style="display:none;">
|
<input type="checkbox" name="breaks-${formCount}-DELETE" id="id_breaks-${formCount}-DELETE" style="display:none;">
|
||||||
<button type="button" class="btn btn-danger btn-sm remove-break">Remove</button>
|
<button type="button" class="remove-break w-full bg-red-500 hover:bg-red-600 text-white text-sm font-medium px-4 py-2 rounded-lg transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="trash-2" class="w-4 h-4 inline mr-1"></i>{% trans "Remove" %}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@ -133,16 +138,20 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
breakTimesContainer.appendChild(newForm);
|
breakTimesContainer.appendChild(newForm);
|
||||||
totalFormsInput.value = formCount + 1;
|
totalFormsInput.value = formCount + 1;
|
||||||
|
lucide.createIcons();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle remove button clicks
|
// Handle remove button clicks
|
||||||
breakTimesContainer.addEventListener('click', function(e) {
|
breakTimesContainer.addEventListener('click', function(e) {
|
||||||
if (e.target.classList.contains('remove-break')) {
|
if (e.target.closest('.remove-break')) {
|
||||||
const form = e.target.closest('.break-time-form');
|
const form = e.target.closest('.break-time-form');
|
||||||
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
|
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
|
||||||
deleteCheckbox.checked = true;
|
deleteCheckbox.checked = true;
|
||||||
form.style.display = 'none';
|
form.style.display = 'none';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialize icons
|
||||||
|
lucide.createIcons();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load static i18n crispy_forms_tags %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{{ schedule.application.name }} - {% trans "Interview Details" %} - ATS{% endblock %}
|
{% block title %}{{ schedule.application.name }} - {% trans "Interview Details" %} - ATS{% endblock %}
|
||||||
|
|
||||||
@ -354,14 +354,29 @@
|
|||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_email_form.subject.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_email_form.subject.id_for_label }}">
|
||||||
{% trans "Subject" %}
|
{% trans "Subject" %}
|
||||||
</label>
|
</label>
|
||||||
{{ interview_email_form.subject }}
|
<input type="text" name="subject" id="{{ interview_email_form.subject.id_for_label }}"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 placeholder:text-gray-400"
|
||||||
|
placeholder="{% trans 'Enter email subject...' %}"
|
||||||
|
value="{% if interview_email_form.subject.value %}{{ interview_email_form.subject.value }}{% endif %}">
|
||||||
|
{% if interview_email_form.subject.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ interview_email_form.subject.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_email_form.message.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_email_form.message.id_for_label }}">
|
||||||
{% trans "Message" %}
|
{% trans "Message" %}
|
||||||
</label>
|
</label>
|
||||||
{{ interview_email_form.message }}
|
<textarea name="message" id="{{ interview_email_form.message.id_for_label }}" rows="5"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[150px] placeholder:text-gray-400"
|
||||||
|
placeholder="{% trans 'Enter your message...' %}"></textarea>
|
||||||
|
{% if interview_email_form.message.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ interview_email_form.message.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3 pt-2">
|
<div class="flex gap-3 pt-2">
|
||||||
@ -407,21 +422,40 @@
|
|||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.new_date.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.new_date.id_for_label }}">
|
||||||
{% trans "New Date" %}
|
{% trans "New Date" %}
|
||||||
</label>
|
</label>
|
||||||
{{ reschedule_form.new_date }}
|
<input type="date" name="new_date" id="{{ reschedule_form.new_date.id_for_label }}"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200">
|
||||||
|
{% if reschedule_form.new_date.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ reschedule_form.new_date.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.new_time.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.new_time.id_for_label }}">
|
||||||
{% trans "New Time" %}
|
{% trans "New Time" %}
|
||||||
</label>
|
</label>
|
||||||
{{ reschedule_form.new_time }}
|
<input type="time" name="new_time" id="{{ reschedule_form.new_time.id_for_label }}"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200">
|
||||||
|
{% if reschedule_form.new_time.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ reschedule_form.new_time.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.reason.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ reschedule_form.reason.id_for_label }}">
|
||||||
{% trans "Reason for Rescheduling" %}
|
{% trans "Reason for Rescheduling" %}
|
||||||
</label>
|
</label>
|
||||||
{{ reschedule_form.reason }}
|
<textarea name="reason" id="{{ reschedule_form.reason.id_for_label }}" rows="3"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
|
||||||
|
placeholder="{% trans 'Enter the reason for rescheduling...' %}"></textarea>
|
||||||
|
{% if reschedule_form.reason.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ reschedule_form.reason.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3 pt-2">
|
<div class="flex gap-3 pt-2">
|
||||||
@ -467,7 +501,14 @@
|
|||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ cancel_form.cancellation_reason.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ cancel_form.cancellation_reason.id_for_label }}">
|
||||||
{% trans "Reason for Cancellation" %}
|
{% trans "Reason for Cancellation" %}
|
||||||
</label>
|
</label>
|
||||||
{{ cancel_form.cancellation_reason }}
|
<textarea name="cancellation_reason" id="{{ cancel_form.cancellation_reason.id_for_label }}" rows="3"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
|
||||||
|
placeholder="{% trans 'Enter the reason for cancellation...' %}"></textarea>
|
||||||
|
{% if cancel_form.cancellation_reason.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ cancel_form.cancellation_reason.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bg-yellow-50 border-l-4 border-yellow-400 rounded-lg p-4">
|
<div class="bg-yellow-50 border-l-4 border-yellow-400 rounded-lg p-4">
|
||||||
@ -520,14 +561,32 @@
|
|||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.interview_result.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.interview_result.id_for_label }}">
|
||||||
{% trans "Interview Result" %}
|
{% trans "Interview Result" %}
|
||||||
</label>
|
</label>
|
||||||
{{ interview_result_form.interview_result }}
|
<select name="interview_result" id="{{ interview_result_form.interview_result.id_for_label }}"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base bg-white focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 cursor-pointer">
|
||||||
|
<option value="">-- {% trans "Select Result" %} --</option>
|
||||||
|
<option value="passed">{% trans "Passed" %}</option>
|
||||||
|
<option value="failed">{% trans "Failed" %}</option>
|
||||||
|
<option value="pending">{% trans "Pending" %}</option>
|
||||||
|
</select>
|
||||||
|
{% if interview_result_form.interview_result.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ interview_result_form.interview_result.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.result_comments.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.result_comments.id_for_label }}">
|
||||||
{% trans "Comments" %}
|
{% trans "Comments" %}
|
||||||
</label>
|
</label>
|
||||||
{{ interview_result_form.result_comments }}
|
<textarea name="result_comments" id="{{ interview_result_form.result_comments.id_for_label }}" rows="3"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
|
||||||
|
placeholder="{% trans 'Enter any comments...' %}"></textarea>
|
||||||
|
{% if interview_result_form.result_comments.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ interview_result_form.result_comments.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3 pt-2">
|
<div class="flex gap-3 pt-2">
|
||||||
@ -569,18 +628,68 @@
|
|||||||
<form method="post" action="{% url 'update_interview_status' schedule.slug %}" class="space-y-4 sm:space-y-5">
|
<form method="post" action="{% url 'update_interview_status' schedule.slug %}" class="space-y-4 sm:space-y-5">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.interview_result.id_for_label }}">
|
||||||
|
{% trans "Interview Result" %}
|
||||||
|
</label>
|
||||||
|
<select name="interview_result" id="{{ interview_result_form.interview_result.id_for_label }}"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base bg-white focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 cursor-pointer">
|
||||||
|
<option value="">-- {% trans "Select Result" %} --</option>
|
||||||
|
<option value="passed">{% trans "Passed" %}</option>
|
||||||
|
<option value="failed">{% trans "Failed" %}</option>
|
||||||
|
<option value="pending">{% trans "Pending" %}</option>
|
||||||
|
</select>
|
||||||
|
{% if interview_result_form.interview_result.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ interview_result_form.interview_result.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_result_form.result_comments.id_for_label }}">
|
||||||
|
{% trans "Comments" %}
|
||||||
|
</label>
|
||||||
|
<textarea name="result_comments" id="{{ interview_result_form.result_comments.id_for_label }}" rows="3"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
|
||||||
|
placeholder="{% trans 'Enter any comments...' %}"></textarea>
|
||||||
|
{% if interview_result_form.result_comments.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ interview_result_form.result_comments.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status.id_for_label }}">
|
||||||
{% trans "Interview Status" %}
|
{% trans "Interview Status" %}
|
||||||
</label>
|
</label>
|
||||||
{{ interview_status_form.status }}
|
<select name="status" id="{{ interview_status_form.status.id_for_label }}"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base bg-white focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 cursor-pointer">
|
||||||
|
<option value="">-- {% trans "Select Status" %} --</option>
|
||||||
|
<option value="scheduled">{% trans "Scheduled" %}</option>
|
||||||
|
<option value="confirmed">{% trans "Confirmed" %}</option>
|
||||||
|
<option value="completed">{% trans "Completed" %}</option>
|
||||||
|
<option value="cancelled">{% trans "Cancelled" %}</option>
|
||||||
|
</select>
|
||||||
|
{% if interview_status_form.status.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ interview_status_form.status.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status_notes.id_for_label }}">
|
<label class="block text-sm font-semibold text-gray-700 mb-2" for="{{ interview_status_form.status_notes.id_for_label }}">
|
||||||
{% trans "Notes" %}
|
{% trans "Notes" %}
|
||||||
</label>
|
</label>
|
||||||
{{ interview_status_form.status_notes }}
|
<textarea name="status_notes" id="{{ interview_status_form.status_notes.id_for_label }}" rows="3"
|
||||||
|
class="w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400"
|
||||||
|
placeholder="{% trans 'Enter any notes...' %}"></textarea>
|
||||||
|
{% if interview_status_form.status_notes.errors %}
|
||||||
|
<div class="mt-1 text-red-600 text-xs">
|
||||||
|
{{ interview_status_form.status_notes.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3 pt-2">
|
<div class="flex gap-3 pt-2">
|
||||||
@ -617,24 +726,39 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Style form fields function
|
// Style form fields function
|
||||||
function styleFormFields() {
|
function styleFormFields() {
|
||||||
// Style text inputs
|
// Style text inputs (including those wrapped by crispy forms)
|
||||||
document.querySelectorAll('input[type="text"], input[type="email"], input[type="number"], input[type="date"], input[type="time"], input[type="url"], input[type="tel"]').forEach(input => {
|
const inputs = document.querySelectorAll('input[type="text"], input[type="email"], input[type="number"], input[type="date"], input[type="time"], input[type="url"], input[type="tel"]');
|
||||||
|
inputs.forEach(input => {
|
||||||
if (!input.classList.contains('styled')) {
|
if (!input.classList.contains('styled')) {
|
||||||
input.className = 'w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 placeholder:text-gray-400 styled';
|
// Remove any existing conflicting classes
|
||||||
|
input.className = input.className.replace(/form-control/g, 'styled');
|
||||||
|
input.className = input.className.replace(/textinput/g, '');
|
||||||
|
// Add base input styling
|
||||||
|
input.classList.add('w-full', 'px-4', 'py-3', 'rounded-xl', 'border-2', 'border-gray-200', 'text-gray-700', 'text-sm', 'sm:text-base', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition-all', 'duration-200', 'placeholder:text-gray-400', 'styled');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Style select dropdowns
|
// Style select dropdowns
|
||||||
document.querySelectorAll('select').forEach(select => {
|
const selects = document.querySelectorAll('select');
|
||||||
|
selects.forEach(select => {
|
||||||
if (!select.classList.contains('styled')) {
|
if (!select.classList.contains('styled')) {
|
||||||
select.className = 'w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base bg-white focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 cursor-pointer styled';
|
// Remove any existing conflicting classes
|
||||||
|
select.className = select.className.replace(/form-control/g, 'styled');
|
||||||
|
select.className = select.className.replace(/select/g, '');
|
||||||
|
// Add base select styling
|
||||||
|
select.classList.add('w-full', 'px-4', 'py-3', 'rounded-xl', 'border-2', 'border-gray-200', 'text-gray-700', 'text-sm', 'sm:text-base', 'bg-white', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition-all', 'duration-200', 'cursor-pointer', 'styled');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Style textareas
|
// Style textareas
|
||||||
document.querySelectorAll('textarea').forEach(textarea => {
|
const textareas = document.querySelectorAll('textarea');
|
||||||
|
textareas.forEach(textarea => {
|
||||||
if (!textarea.classList.contains('styled')) {
|
if (!textarea.classList.contains('styled')) {
|
||||||
textarea.className = 'w-full px-4 py-3 rounded-xl border-2 border-gray-200 text-gray-700 text-sm sm:text-base focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition-all duration-200 resize-y min-h-[100px] placeholder:text-gray-400 styled';
|
// Remove any existing conflicting classes
|
||||||
|
textarea.className = textarea.className.replace(/form-control/g, 'styled');
|
||||||
|
textarea.className = textarea.className.replace(/textarea/g, '');
|
||||||
|
// Add base textarea styling
|
||||||
|
textarea.classList.add('w-full', 'px-4', 'py-3', 'rounded-xl', 'border-2', 'border-gray-200', 'text-gray-700', 'text-sm', 'sm:text-base', 'focus:ring-2', 'focus:ring-temple-red/20', 'focus:border-temple-red', 'outline-none', 'transition-all', 'duration-200', 'resize-y', 'min-h-[100px]', 'placeholder:text-gray-400', 'styled');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -652,10 +776,16 @@ function openModal(modalId) {
|
|||||||
modal.classList.remove('hidden');
|
modal.classList.remove('hidden');
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
// Style form fields after modal opens
|
// Style form fields after modal opens with multiple retries
|
||||||
setTimeout(() => {
|
setTimeout(function() {
|
||||||
styleFormFields();
|
styleFormFields();
|
||||||
}, 50);
|
}, 100);
|
||||||
|
setTimeout(function() {
|
||||||
|
styleFormFields();
|
||||||
|
}, 300);
|
||||||
|
setTimeout(function() {
|
||||||
|
styleFormFields();
|
||||||
|
}, 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -691,7 +821,7 @@ async function copyJoinUrl() {
|
|||||||
messageElement.classList.add('text-green-600');
|
messageElement.classList.add('text-green-600');
|
||||||
messageElement.classList.remove('text-red-600');
|
messageElement.classList.remove('text-red-600');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(function() {
|
||||||
messageElement.textContent = '';
|
messageElement.textContent = '';
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@ -11,25 +11,25 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-6">
|
<div class="container mx-auto px-3 sm:px-4 lg:px-8 py-6">
|
||||||
|
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<nav class="mb-6" aria-label="breadcrumb">
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||||
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-kaauh-blue transition flex items-center gap-1">
|
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
|
||||||
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Home" %}
|
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Dashboard" %}
|
||||||
</a></li>
|
</a></li>
|
||||||
<li class="text-gray-400">/</li>
|
<li class="text-gray-400">/</li>
|
||||||
<li><a href="{% url 'job_list' %}" class="text-gray-500 hover:text-kaauh-blue transition">{% trans "Jobs" %}</a></li>
|
<li><a href="{% url 'job_list' %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Jobs" %}</a></li>
|
||||||
<li class="text-gray-400">/</li>
|
<li class="text-gray-400">/</li>
|
||||||
<li><a href="{% url 'job_detail' job.slug %}" class="text-gray-500 hover:text-kaauh-blue transition">{{ job.title }}</a></li>
|
<li><a href="{% url 'job_detail' job.slug %}" class="text-gray-500 hover:text-temple-red transition">{{ job.title }}</a></li>
|
||||||
<li class="text-gray-400">/</li>
|
<li class="text-gray-400">/</li>
|
||||||
<li class="text-temple-red font-semibold">{% trans "Applicants" %}</li>
|
<li class="text-temple-red font-semibold">{% trans "Applications" %}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Job Header -->
|
<!-- Job Header -->
|
||||||
<div class="bg-gradient-to-br from-kaauh-blue to-[#004d57] text-white rounded-2xl p-6 mb-6">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white rounded-2xl p-6 mb-6">
|
||||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
|
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h1 class="text-2xl font-bold mb-2">{{ job.title }}</h1>
|
<h1 class="text-2xl font-bold mb-2">{{ job.title }}</h1>
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<div class="flex flex-wrap gap-4 text-sm">
|
<div class="flex flex-wrap gap-4 text-sm">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<i data-lucide="users" class="w-4 h-4"></i>
|
<i data-lucide="users" class="w-4 h-4"></i>
|
||||||
<span>{{ total_applications }} {% trans "Total Applicants" %}</span>
|
<span>{{ total_applications }} {% trans "Total Applicantions" %}</span>
|
||||||
</div>
|
</div>
|
||||||
{% if job.max_applications %}
|
{% if job.max_applications %}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@ -62,8 +62,8 @@
|
|||||||
|
|
||||||
<!-- Stats Section -->
|
<!-- Stats Section -->
|
||||||
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-6">
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4 mb-6">
|
||||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 text-center border-l-4 border-kaauh-blue">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 text-center border-l-4 border-temple-red">
|
||||||
<div class="text-3xl font-bold text-kaauh-blue mb-1">{{ total_applications }}</div>
|
<div class="text-3xl font-bold text-temple-red mb-1">{{ total_applications }}</div>
|
||||||
<div class="text-xs text-gray-600">{% trans "Total Applications" %}</div>
|
<div class="text-xs text-gray-600">{% trans "Total Applications" %}</div>
|
||||||
</div>
|
</div>
|
||||||
{% for stage_key, stage_data in stage_stats.items %}
|
{% for stage_key, stage_data in stage_stats.items %}
|
||||||
@ -88,11 +88,11 @@
|
|||||||
<label for="search" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Search Applicants" %}</label>
|
<label for="search" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Search Applicants" %}</label>
|
||||||
<input type="text" id="search" name="q" value="{{ search_query }}"
|
<input type="text" id="search" name="q" value="{{ search_query }}"
|
||||||
placeholder="{% trans 'Search by name, email, or phone...' %}"
|
placeholder="{% trans 'Search by name, email, or phone...' %}"
|
||||||
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition">
|
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="stage" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Application Stage" %}</label>
|
<label for="stage" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Application Stage" %}</label>
|
||||||
<select id="stage" name="stage" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition">
|
<select id="stage" name="stage" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
<option value="">{% trans "All Stages" %}</option>
|
<option value="">{% trans "All Stages" %}</option>
|
||||||
{% for key, value in stage_choices %}
|
{% for key, value in stage_choices %}
|
||||||
<option value="{{ key }}" {% if stage_filter == key %}selected{% endif %}>
|
<option value="{{ key }}" {% if stage_filter == key %}selected{% endif %}>
|
||||||
@ -105,27 +105,27 @@
|
|||||||
<label for="min_ai_score" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Min AI Score" %}</label>
|
<label for="min_ai_score" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Min AI Score" %}</label>
|
||||||
<input type="number" id="min_ai_score" name="min_ai_score" value="{{ min_ai_score }}"
|
<input type="number" id="min_ai_score" name="min_ai_score" value="{{ min_ai_score }}"
|
||||||
placeholder="0" min="0" max="100"
|
placeholder="0" min="0" max="100"
|
||||||
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition">
|
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="max_ai_score" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Max AI Score" %}</label>
|
<label for="max_ai_score" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Max AI Score" %}</label>
|
||||||
<input type="number" id="max_ai_score" name="max_ai_score" value="{{ max_ai_score }}"
|
<input type="number" id="max_ai_score" name="max_ai_score" value="{{ max_ai_score }}"
|
||||||
placeholder="100" min="0" max="100"
|
placeholder="100" min="0" max="100"
|
||||||
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition">
|
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="date_from" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "From Date" %}</label>
|
<label for="date_from" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "From Date" %}</label>
|
||||||
<input type="date" id="date_from" name="date_from" value="{{ date_from }}"
|
<input type="date" id="date_from" name="date_from" value="{{ date_from }}"
|
||||||
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition">
|
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="date_to" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "To Date" %}</label>
|
<label for="date_to" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "To Date" %}</label>
|
||||||
<input type="date" id="date_to" name="date_to" value="{{ date_to }}"
|
<input type="date" id="date_to" name="date_to" value="{{ date_to }}"
|
||||||
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition">
|
class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="sort" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Sort By" %}</label>
|
<label for="sort" class="block text-sm font-semibold text-gray-700 mb-2">{% trans "Sort By" %}</label>
|
||||||
<select id="sort" name="sort" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-kaauh-blue/20 focus:border-kaauh-blue outline-none transition">
|
<select id="sort" name="sort" class="w-full px-4 py-2.5 border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
<option value="-created_at" {% if sort_by == '-created_at' %}selected{% endif %}>{% trans "Newest First" %}</option>
|
<option value="-created_at" {% if sort_by == '-created_at' %}selected{% endif %}>{% trans "Newest First" %}</option>
|
||||||
<option value="created_at" {% if sort_by == 'created_at' %}selected{% endif %}>{% trans "Oldest First" %}</option>
|
<option value="created_at" {% if sort_by == 'created_at' %}selected{% endif %}>{% trans "Oldest First" %}</option>
|
||||||
<option value="person__first_name" {% if sort_by == 'person__first_name' %}selected{% endif %}>{% trans "Name (A-Z)" %}</option>
|
<option value="person__first_name" {% if sort_by == 'person__first_name' %}selected{% endif %}>{% trans "Name (A-Z)" %}</option>
|
||||||
@ -135,7 +135,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<button type="submit" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
|
||||||
<i data-lucide="filter" class="w-4 h-4"></i>
|
<i data-lucide="filter" class="w-4 h-4"></i>
|
||||||
{% trans "Apply Filters" %}
|
{% trans "Apply Filters" %}
|
||||||
</button>
|
</button>
|
||||||
@ -201,7 +201,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
<a href="{% url 'application_detail' application.slug %}" class="inline-flex items-center gap-2 bg-blue-500 hover:bg-blue-600 text-white font-medium px-3 py-2 rounded-lg text-xs transition">
|
<a href="{% url 'application_detail' application.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-3 py-2 rounded-lg text-xs transition">
|
||||||
<i data-lucide="file-text" class="w-3 h-3"></i>
|
<i data-lucide="file-text" class="w-3 h-3"></i>
|
||||||
{% trans "Application" %}
|
{% trans "Application" %}
|
||||||
</a>
|
</a>
|
||||||
@ -236,7 +236,7 @@
|
|||||||
{% trans "There are currently no applicants for this job." %}
|
{% trans "There are currently no applicants for this job." %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<a href="{% url 'job_applicants' job.slug %}" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-6 py-3 rounded-xl text-sm transition shadow-sm hover:shadow-md">
|
<a href="{% url 'job_applicants' job.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-6 py-3 rounded-xl text-sm transition shadow-sm hover:shadow-md">
|
||||||
<i data-lucide="refresh-cw" class="w-5 h-5"></i>
|
<i data-lucide="refresh-cw" class="w-5 h-5"></i>
|
||||||
{% trans "Clear Filters" %}
|
{% trans "Clear Filters" %}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-6">
|
<div class="container mx-auto px-3 sm:px-4 lg:px-8 py-6">
|
||||||
|
|
||||||
<!-- Breadcrumb -->
|
<!-- Breadcrumb -->
|
||||||
<nav class="mb-6" aria-label="breadcrumb">
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
@ -24,18 +24,18 @@
|
|||||||
<li class="text-gray-400">/</li>
|
<li class="text-gray-400">/</li>
|
||||||
<li><a href="{% url 'job_detail' job.slug %}" class="text-gray-500 hover:text-temple-red transition">{{ job.title }}</a></li>
|
<li><a href="{% url 'job_detail' job.slug %}" class="text-gray-500 hover:text-temple-red transition">{{ job.title }}</a></li>
|
||||||
<li class="text-gray-400">/</li>
|
<li class="text-gray-400">/</li>
|
||||||
<li class="text-temple-red font-semibold">{% trans "Applicants" %}</li>
|
<li class="text-temple-red font-semibold">{% trans "Applications" %}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
<div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4 mb-6">
|
||||||
<div>
|
<div class="text-gray-900">
|
||||||
<h1 class="text-2xl font-bold text-gray-900 mb-1 flex items-center gap-3">
|
<h1 class="text-2xl font-bold mb-1 flex items-center gap-3">
|
||||||
<a href="{% url 'job_detail' job.slug %}" class="text-temple-red hover:text-[#7a1a29] transition">
|
<a href="{% url 'job_detail' job.slug %}" class="text-temple-red hover:text-[#7a1a29] transition">
|
||||||
<i data-lucide="arrow-left" class="w-6 h-6"></i>
|
<i data-lucide="arrow-left" class="w-6 h-6"></i>
|
||||||
</a>
|
</a>
|
||||||
{% trans "Applicants for" %} "{{ job.title }}"
|
<span>{% trans "Applicants for" %} "{{ job.title }}"</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'application_create_for_job' job.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md whitespace-nowrap">
|
<a href="{% url 'application_create_for_job' job.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md whitespace-nowrap">
|
||||||
|
|||||||
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,58 +1,63 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{{ title }} - ATS{% endblock %}
|
{% block title %}{{ title }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<div class="row justify-content-center">
|
<div class="flex justify-center">
|
||||||
<div class="col-md-6">
|
<div class="w-full max-w-2xl">
|
||||||
<div class="card">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<div class="card-header bg-warning text-white">
|
<div class="bg-amber-500 text-white p-5">
|
||||||
<h5 class="mb-0">
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
<i data-lucide="alert-triangle" class="w-6 h-6"></i>
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<div class="alert alert-warning" role="alert">
|
<div class="bg-amber-50 border border-amber-200 rounded-xl p-4 mb-6 flex items-start gap-3">
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
<i data-lucide="info" class="w-5 h-5 text-amber-600 shrink-0 mt-0.5"></i>
|
||||||
{{ message }}
|
<p class="text-amber-800">{{ message }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-3">
|
<div class="bg-gray-50 rounded-xl p-5 mb-6">
|
||||||
<div class="me-3">
|
<div class="flex flex-wrap items-center gap-4 mb-4">
|
||||||
<strong>{% trans "Agency:" %}</strong> {{ access_link.assignment.agency.name }}
|
<div>
|
||||||
|
<strong class="text-gray-700">{% trans "Agency:" %}</strong> {{ access_link.assignment.agency.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="me-3">
|
<div>
|
||||||
<strong>{% trans "Job:" %}</strong> {{ access_link.assignment.job.title }}
|
<strong class="text-gray-700">{% trans "Job:" %}</strong> {{ access_link.assignment.job.title }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-3">
|
<div class="flex flex-wrap items-center gap-4 mb-4">
|
||||||
<div class="me-3">
|
<div>
|
||||||
<strong>{% trans "Current Status:" %}</strong>
|
<strong class="text-gray-700">{% trans "Current Status:" %}</strong>
|
||||||
<span class="badge bg-{{ 'success' if access_link.is_active else 'danger' }}">
|
<span class="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium {{ 'bg-green-100 text-green-800' if access_link.is_active else 'bg-red-100 text-red-800' }}">
|
||||||
{{ 'Active' if access_link.is_active else 'Inactive' }}
|
{% if access_link.is_active %}
|
||||||
|
{% trans "Active" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Inactive" %}
|
||||||
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-3">
|
<div class="flex flex-wrap items-center gap-4 mb-4">
|
||||||
<div class="me-3">
|
<div>
|
||||||
<strong>{% trans "Expires:" %}</strong> {{ access_link.expires_at|date:"Y-m-d H:i" }}
|
<strong class="text-gray-700">{% trans "Expires:" %}</strong> {{ access_link.expires_at|date:"Y-m-d H:i" }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-3">
|
<div class="flex flex-wrap items-center gap-4 mb-4">
|
||||||
<div class="me-3">
|
<div>
|
||||||
<strong>{% trans "Access Count:" %}</strong> {{ access_link.access_count }}
|
<strong class="text-gray-700">{% trans "Access Count:" %}</strong> {{ access_link.access_count }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-3">
|
<div class="flex flex-wrap items-center gap-4">
|
||||||
<div class="me-3">
|
<div>
|
||||||
<strong>{% trans "Last Accessed:" %}</strong>
|
<strong class="text-gray-700">{% trans "Last Accessed:" %}</strong>
|
||||||
{% if access_link.last_accessed %}
|
{% if access_link.last_accessed %}
|
||||||
{{ access_link.last_accessed|date:"Y-m-d H:i" }}
|
{{ access_link.last_accessed|date:"Y-m-d H:i" }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -60,16 +65,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<form method="post" action="{% url request.resolver_match.url_name %}">
|
<form method="post" action="{% url request.resolver_match.url_name %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
<div class="flex flex-col sm:flex-row gap-3 justify-end">
|
||||||
<a href="{{ cancel_url }}" class="btn btn-secondary">
|
<a href="{{ cancel_url }}" class="inline-flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-6 py-2.5 rounded-xl transition">
|
||||||
<i class="fas fa-times me-2"></i>
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="btn btn-warning">
|
<button type="submit" class="inline-flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-{{ 'toggle-on' if title == 'Reactivate Access Link' else 'toggle-off' }} me-2"></i>
|
<i data-lucide="{% if title == 'Reactivate Access Link' %}toggle-right{% else %}toggle-left{% endif %}" class="w-4 h-4"></i>
|
||||||
{{ title }}
|
{{ title }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -79,4 +85,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -4,184 +4,151 @@
|
|||||||
{% block title %}{% trans "Access Link Details" %} - ATS{% endblock %}
|
{% block title %}{% trans "Access Link Details" %} - ATS{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4 px-3 py-3">
|
<!-- Header -->
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6 px-3 py-3">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 class="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
|
||||||
<i class="fas fa-link me-2"></i>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="link" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
{% trans "Access Link Details" %}
|
{% trans "Access Link Details" %}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">{% trans "Secure access link for agency candidate submissions" %}</p>
|
<p class="text-gray-600">{% trans "Secure access link for agency candidate submissions" %}</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="btn btn-outline-secondary">
|
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="inline-flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-6 py-2.5 rounded-xl transition">
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Assignment" %}
|
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Assignment" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<div class="col-md-8">
|
<!-- Main Content -->
|
||||||
<div class="kaauh-card shadow-sm mb-4 px-3 py-3">
|
<div class="lg:col-span-2">
|
||||||
<div class="card-body">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
<div class="p-6">
|
||||||
<h5 class="card-title mb-0">
|
<div class="flex justify-between items-start mb-6">
|
||||||
<i class="fas fa-shield-alt me-2 text-primary"></i>
|
<h5 class="text-xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="shield" class="w-5 h-5 text-blue-600"></i>
|
||||||
|
</div>
|
||||||
{% trans "Access Information" %}
|
{% trans "Access Information" %}
|
||||||
</h5>
|
</h5>
|
||||||
<span class="badge {% if access_link.is_active %}bg-success{% else %}bg-danger{% endif %}">
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium {% if access_link.is_active %}bg-green-100 text-green-800{% else %}bg-red-100 text-red-800{% endif %}">
|
||||||
{% if access_link.is_active %}{% trans "Active" %}{% else %}{% trans "Inactive" %}{% endif %}
|
{% if access_link.is_active %}{% trans "Active" %}{% else %}{% trans "Inactive" %}{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="col-md-6 mb-3">
|
<div>
|
||||||
<label class="form-label text-muted small">{% trans "Assignment" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Assignment" %}</label>
|
||||||
<div class="fw-semibold">
|
<div class="font-semibold text-gray-900">
|
||||||
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="text-decoration-none">
|
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="hover:text-temple-red transition">
|
||||||
{{ access_link.assignment.agency.name }} - {{ access_link.assignment.job.title }}
|
{{ access_link.assignment.agency.name }} - {{ access_link.assignment.job.title }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<div>
|
||||||
<label class="form-label text-muted small">{% trans "Agency" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Agency" %}</label>
|
||||||
<div class="fw-semibold">{{ access_link.assignment.agency.name }}</div>
|
<div class="font-semibold text-gray-900">{{ access_link.assignment.agency.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<div>
|
||||||
<label class="form-label text-muted small">{% trans "Created At" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Created At" %}</label>
|
||||||
<div class="fw-semibold">{{ access_link.created_at|date:"Y-m-d H:i" }}</div>
|
<div class="font-semibold text-gray-900">{{ access_link.created_at|date:"Y-m-d H:i" }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<div>
|
||||||
<label class="form-label text-muted small">{% trans "Expires At" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Expires At" %}</label>
|
||||||
<div class="fw-semibold {% if access_link.is_expired %}text-danger{% endif %}">
|
<div class="font-semibold {% if access_link.is_expired %}text-red-600{% endif %} text-gray-900">
|
||||||
{{ access_link.expires_at|date:"Y-m-d H:i" }}
|
{{ access_link.expires_at|date:"Y-m-d H:i" }}
|
||||||
{% if access_link.is_expired %}
|
{% if access_link.is_expired %}
|
||||||
<span class="badge bg-danger ms-2">{% trans "Expired" %}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-100 text-red-800 ml-2">{% trans "Expired" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<div>
|
||||||
<label class="form-label text-muted small">{% trans "Max Candidates" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Max Candidates" %}</label>
|
||||||
<div class="fw-semibold">{{ access_link.assignment.max_candidates }}</div>
|
<div class="font-semibold text-gray-900">{{ access_link.assignment.max_candidates }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<div>
|
||||||
<label class="form-label text-muted small">{% trans "Candidates Submitted" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Candidates Submitted" %}</label>
|
||||||
<div class="fw-semibold">{{ access_link.assignment.candidates_submitted }}</div>
|
<div class="font-semibold text-gray-900">{{ access_link.assignment.candidates_submitted }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% comment %} <div class="kaauh-card shadow-sm">
|
<!-- Sidebar -->
|
||||||
<div class="card-body px-3 py-3">
|
<div class="lg:col-span-1 space-y-6">
|
||||||
<h5 class="card-title mb-3">
|
<!-- Usage Statistics -->
|
||||||
<i class="fas fa-key me-2 text-warning"></i>
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
{% trans "Access Credentials" %}
|
<div class="p-6">
|
||||||
</h5>
|
<h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||||
<div class="mb-3">
|
<i data-lucide="trending-up" class="w-5 h-5 text-blue-600"></i>
|
||||||
<label class="form-label text-muted small">{% trans "Login URL" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ request.scheme }}://{{ request.get_host }}{% url 'agency_portal_login' %}"
|
|
||||||
class="form-control font-monospace" id="loginUrl">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('loginUrl')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label text-muted small">{% trans "Access Token" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ access_link.unique_token }}"
|
|
||||||
class="form-control font-monospace" id="accessToken">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessToken')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label text-muted small">{% trans "Password" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ access_link.access_password }}"
|
|
||||||
class="form-control font-monospace" id="accessPassword">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessPassword')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
|
||||||
{% trans "Share these credentials securely with the agency. They can use this information to log in and submit candidates." %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> {% endcomment %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="kaauh-card shadow-sm mb-4 px-3 py-3">
|
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title mb-3">
|
|
||||||
<i class="fas fa-chart-line me-2 text-info"></i>
|
|
||||||
{% trans "Usage Statistics" %}
|
{% trans "Usage Statistics" %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="space-y-4">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-muted">{% trans "Total Accesses" %}</span>
|
<span class="text-gray-600">{% trans "Total Accesses" %}</span>
|
||||||
<span class="fw-semibold">{{ access_link.access_count }}</span>
|
<span class="font-bold text-gray-900">{{ access_link.access_count }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-muted">{% trans "Last Accessed" %}</span>
|
<span class="text-gray-600">{% trans "Last Accessed" %}</span>
|
||||||
<span class="fw-semibold">
|
<span class="font-semibold text-gray-900">
|
||||||
{% if access_link.last_accessed %}
|
{% if access_link.last_accessed %}
|
||||||
{{ access_link.last_accessed|date:"Y-m-d H:i" }}
|
{{ access_link.last_accessed|date:"Y-m-d H:i" }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">{% trans "Never" %}</span>
|
<span class="text-gray-500">{% trans "Never" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-muted">{% trans "Submissions" %}</span>
|
<span class="text-gray-600">{% trans "Submissions" %}</span>
|
||||||
<span class="fw-semibold">{{ access_link.assignment.candidates_submitted }}/{{ access_link.assignment.max_candidates }}</span>
|
<span class="font-bold text-gray-900">{{ access_link.assignment.candidates_submitted }}/{{ access_link.assignment.max_candidates }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="progress" style="height: 8px;">
|
|
||||||
{% widthratio access_link.assignment.candidates_submitted access_link.assignment.max_candidates 100 as progress_percent %}
|
{% widthratio access_link.assignment.candidates_submitted access_link.assignment.max_candidates 100 as progress_percent %}
|
||||||
<div class="progress-bar {% if progress_percent >= 80 %}bg-danger{% elif progress_percent >= 60 %}bg-warning{% else %}bg-success{% endif %}"
|
<div class="mt-4">
|
||||||
style="width: {{ progress_percent }}%"></div>
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||||
|
<div class="h-2 rounded-full {% if progress_percent >= 80 %}bg-red-500{% elif progress_percent >= 60 %}bg-yellow-500{% else %}bg-green-500{% endif %}" style="width: {{ progress_percent }}%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="text-right text-sm text-gray-600 mt-1">{{ progress_percent }}%</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kaauh-card shadow-sm px-3 py-3">
|
<!-- Actions -->
|
||||||
<div class="card-body">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<h5 class="card-title mb-3">
|
<div class="p-6">
|
||||||
<i class="fas fa-cog me-2 text-secondary"></i>
|
<h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<div class="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="settings" class="w-5 h-5 text-gray-600"></i>
|
||||||
|
</div>
|
||||||
{% trans "Actions" %}
|
{% trans "Actions" %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="d-grid gap-2">
|
<div class="space-y-3">
|
||||||
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="btn btn-outline-secondary btn-sm">
|
<a href="{% url 'agency_assignment_detail' access_link.assignment.slug %}" class="inline-flex items-center justify-center w-full gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2.5 rounded-xl transition">
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Assignment" %}
|
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Assignment" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if access_link.is_active and not access_link.is_expired %}
|
{% if access_link.is_active and not access_link.is_expired %}
|
||||||
<button class="btn btn-warning btn-sm" onclick="confirmDeactivate()">
|
<button onclick="confirmDeactivate()" class="inline-flex items-center justify-center w-full gap-2 bg-yellow-500 hover:bg-yellow-600 text-white font-semibold px-4 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-pause me-1"></i> {% trans "Deactivate" %}
|
<i data-lucide="pause" class="w-4 h-4"></i> {% trans "Deactivate" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if access_link.is_expired or not access_link.is_active %}
|
{% if access_link.is_expired or not access_link.is_active %}
|
||||||
<button class="btn btn-success btn-sm" onclick="confirmReactivate()">
|
<button onclick="confirmReactivate()" class="inline-flex items-center justify-center w-full gap-2 bg-green-500 hover:bg-green-600 text-white font-semibold px-4 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-play me-1"></i> {% trans "Reactivate" %}
|
<i data-lucide="play" class="w-4 h-4"></i> {% trans "Reactivate" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -190,41 +157,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
<script>
|
||||||
function copyToClipboard(elementId) {
|
|
||||||
const element = document.getElementById(elementId);
|
|
||||||
element.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
|
|
||||||
// Show feedback
|
|
||||||
const button = element.nextElementSibling;
|
|
||||||
const originalHTML = button.innerHTML;
|
|
||||||
button.innerHTML = '<i class="fas fa-check"></i>';
|
|
||||||
button.classList.add('btn-success');
|
|
||||||
button.classList.remove('btn-outline-secondary');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
button.innerHTML = originalHTML;
|
|
||||||
button.classList.remove('btn-success');
|
|
||||||
button.classList.add('btn-outline-secondary');
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmDeactivate() {
|
function confirmDeactivate() {
|
||||||
if (confirm('{% trans "Are you sure you want to deactivate this access link? Agencies will no longer be able to use it." %}')) {
|
if (confirm('{% trans "Are you sure you want to deactivate this access link? Agencies will no longer be able to use it." %}')) {
|
||||||
// Submit form to deactivate
|
|
||||||
window.location.href = '{% url "agency_access_link_deactivate" access_link.slug %}';
|
window.location.href = '{% url "agency_access_link_deactivate" access_link.slug %}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmReactivate() {
|
function confirmReactivate() {
|
||||||
if (confirm('{% trans "Are you sure you want to reactivate this access link?" %}')) {
|
if (confirm('{% trans "Are you sure you want to reactivate this access link?" %}')) {
|
||||||
// Submit form to reactivate
|
|
||||||
window.location.href = '{% url "agency_access_link_reactivate" access_link.slug %}';
|
window.location.href = '{% url "agency_access_link_reactivate" access_link.slug %}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
lucide.createIcons();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -3,284 +3,159 @@
|
|||||||
|
|
||||||
{% block title %}{{ assignment.agency.name }} - {{ assignment.job.title }} - ATS{% endblock %}
|
{% block title %}{{ assignment.agency.name }} - {{ assignment.job.title }} - ATS{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* KAAT-S UI Variables */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-success: #28a745;
|
|
||||||
--kaauh-info: #17a2b8;
|
|
||||||
--kaauh-danger: #dc3545;
|
|
||||||
--kaauh-warning: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kaauh-card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.3em 0.7em;
|
|
||||||
border-radius: 0.35rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.status-ACTIVE { background-color: var(--kaauh-success); color: white; }
|
|
||||||
.status-EXPIRED { background-color: var(--kaauh-danger); color: white; }
|
|
||||||
.status-COMPLETED { background-color: var(--kaauh-info); color: white; }
|
|
||||||
.status-CANCELLED { background-color: var(--kaauh-warning); color: #856404; }
|
|
||||||
|
|
||||||
.progress-ring {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-ring-circle {
|
|
||||||
transition: stroke-dashoffset 0.35s;
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-ring-text {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-item {
|
|
||||||
border-left: 4px solid var(--kaauh-teal);
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border-radius: 0 0.5rem 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-item.unread {
|
|
||||||
border-left-color: var(--kaauh-info);
|
|
||||||
background-color: #e7f3ff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="px-4 py-6">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-6">
|
||||||
<div>
|
<div class="flex-1">
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 class="text-2xl font-bold text-temple-dark mb-1 flex items-center gap-2">
|
||||||
<i class="fas fa-tasks me-2"></i>
|
<i data-lucide="tasks" class="w-7 h-7"></i>
|
||||||
{{ assignment.agency.name }} - {{ assignment.job.title }}
|
{{ assignment.agency.name }} - {{ assignment.job.title }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">
|
<p class="text-gray-600">
|
||||||
{% trans "Assignment Details and Management" %}
|
{% trans "Assignment Details and Management" %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="flex gap-2">
|
||||||
<a href="{% url 'agency_assignment_list' %}" class="btn btn-outline-secondary me-2">
|
<a href="{% url 'agency_assignment_list' %}"
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Assignments" %}
|
class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
{% trans "Back to Assignments" %}
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'agency_assignment_update' assignment.slug %}" class="btn btn-main-action">
|
<a href="{% url 'agency_assignment_update' assignment.slug %}"
|
||||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Assignment" %}
|
class="inline-flex items-center gap-2 bg-temple-red hover:bg-red-800 text-white font-semibold px-4 py-2 rounded-lg transition shadow-md hover:shadow-lg">
|
||||||
|
<i data-lucide="edit" class="w-4 h-4"></i>
|
||||||
|
{% trans "Edit Assignment" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
<!-- Main Content -->
|
||||||
<!-- Assignment Overview -->
|
<div class="lg:col-span-2 space-y-6">
|
||||||
<div class="col-lg-8">
|
|
||||||
<!-- Assignment Details Card -->
|
<!-- Assignment Details Card -->
|
||||||
<div class="kaauh-card p-4 mb-4">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
<div class="p-6">
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
<h5 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||||
|
<i data-lucide="info" class="w-5 h-5"></i>
|
||||||
{% trans "Assignment Details" %}
|
{% trans "Assignment Details" %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="col-md-6">
|
<div class="space-y-4">
|
||||||
<div class="mb-3">
|
|
||||||
<label class="text-muted small">{% trans "Agency" %}</label>
|
|
||||||
<div class="fw-bold">{{ assignment.agency.name }}</div>
|
|
||||||
<div class="text-muted small">{{ assignment.agency.contact_person }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="text-muted small">{% trans "Job" %}</label>
|
|
||||||
<div class="fw-bold">{{ assignment.job.title }}</div>
|
|
||||||
<div class="text-muted small">{{ assignment.job.department }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="text-muted small">{% trans "Status" %}</label>
|
|
||||||
<div>
|
<div>
|
||||||
<span class="status-badge bg-primary-theme text-white">
|
<label class="text-sm text-gray-500 font-medium block mb-1">{% trans "Agency" %}</label>
|
||||||
|
<div class="font-semibold text-gray-900">{{ assignment.agency.name }}</div>
|
||||||
|
<div class="text-sm text-gray-600">{{ assignment.agency.contact_person }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-sm text-gray-500 font-medium block mb-1">{% trans "Job" %}</label>
|
||||||
|
<div class="font-semibold text-gray-900">{{ assignment.job.title }}</div>
|
||||||
|
<div class="text-sm text-gray-600">{{ assignment.job.department }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="text-sm text-gray-500 font-medium block mb-1">{% trans "Status" %}</label>
|
||||||
|
<span class="status-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-temple-red text-white">
|
||||||
{{ assignment.get_status_display }}
|
{{ assignment.get_status_display }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div class="mb-3">
|
<label class="text-sm text-gray-500 font-medium block mb-1">{% trans "Deadline" %}</label>
|
||||||
<label class="text-muted small">{% trans "Deadline" %}</label>
|
<div class="{% if assignment.is_expired %}text-red-600{% else %}text-gray-600{% endif %} flex items-center gap-1">
|
||||||
<div class="{% if assignment.is_expired %}text-danger{% else %}text-muted{% endif %}">
|
<i data-lucide="calendar" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-calendar-alt me-1"></i>
|
|
||||||
{{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
{{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
||||||
</div>
|
</div>
|
||||||
{% if assignment.is_expired %}
|
{% if assignment.is_expired %}
|
||||||
<small class="text-danger">
|
<span class="text-sm text-red-600 flex items-center gap-1 mt-1">
|
||||||
<i class="fas fa-exclamation-triangle me-1"></i>{% trans "Expired" %}
|
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
|
||||||
</small>
|
{% trans "Expired" %}
|
||||||
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if assignment.admin_notes %}
|
{% if assignment.admin_notes %}
|
||||||
<div class="mt-3 pt-3 border-top">
|
<div class="mt-6 pt-4 border-t border-gray-200">
|
||||||
<label class="text-muted small">{% trans "Admin Notes" %}</label>
|
<label class="text-sm text-gray-500 font-medium block mb-2">{% trans "Admin Notes" %}</label>
|
||||||
<div class="text-muted">{{ assignment.admin_notes }}</div>
|
<div class="text-gray-700">{{ assignment.admin_notes }}</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% comment %} <div class="kaauh-card shadow-sm mb-4">
|
|
||||||
<div class="card-body my-2">
|
|
||||||
<h5 class="card-title mb-3 mx-2">
|
|
||||||
<i class="fas fa-key me-2 text-warning"></i>
|
|
||||||
{% trans "Access Credentials" %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="mb-3 mx-2">
|
|
||||||
<label class="form-label text-muted small">{% trans "Login URL" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ request.scheme }}://{{ request.get_host }}{% url 'agency_portal_login' %}"
|
|
||||||
class="form-control font-monospace" id="loginUrl">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('loginUrl')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 mx-2">
|
|
||||||
<label class="form-label text-muted small">{% trans "Access Token" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ access_link.unique_token }}"
|
|
||||||
class="form-control font-monospace" id="accessToken">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessToken')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3 mx-2">
|
|
||||||
<label class="form-label text-muted small">{% trans "Password" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ access_link.access_password }}"
|
|
||||||
class="form-control font-monospace" id="accessPassword">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessPassword')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-info mx-2">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
|
||||||
{% trans "Share these credentials securely with the agency. They can use this information to log in and submit applications." %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if access_link %}
|
|
||||||
<a href="{% url 'agency_access_link_detail' access_link.slug %}"
|
|
||||||
class="btn btn-outline-info btn-sm mx-2">
|
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Access Links Details" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div> {% endcomment %}
|
|
||||||
|
|
||||||
<!-- Applications Card -->
|
<!-- Applications Card -->
|
||||||
<div class="kaauh-card p-4">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="p-6">
|
||||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
<div class="flex justify-between items-center mb-4">
|
||||||
<i class="fas fa-users me-2"></i>
|
<h5 class="text-lg font-semibold text-temple-dark flex items-center gap-2">
|
||||||
|
<i data-lucide="users" class="w-5 h-5"></i>
|
||||||
{% trans "Submitted Applications" %} ({{ total_applications }})
|
{% trans "Submitted Applications" %} ({{ total_applications }})
|
||||||
</h5>
|
</h5>
|
||||||
{% if access_link %}
|
{% if access_link %}
|
||||||
<a href="{% url 'agency_portal_login' %}" target="_blank" class="btn btn-outline-info btn-sm">
|
<a href="{% url 'agency_portal_login' %}" target="_blank"
|
||||||
<i class="fas fa-external-link-alt me-1"></i> {% trans "Preview Portal" %}
|
class="inline-flex items-center gap-2 border border-blue-500 text-blue-600 hover:bg-blue-50 px-3 py-1.5 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="external-link" class="w-4 h-4"></i>
|
||||||
|
<span class="hidden sm:inline">{% trans "Preview Portal" %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if applications %}
|
{% if applications %}
|
||||||
<div class="table-responsive">
|
<div class="overflow-x-auto">
|
||||||
<table class="table table-hover">
|
<table class="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="border-b border-gray-200">
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Application"%}</th>
|
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Contact" %}
|
{% trans "Application" %}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Stage" %}
|
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
|
{% trans "Contact" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
|
{% trans "Stage" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
|
{% trans "Submitted" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
|
{% trans "Actions" %}
|
||||||
</th>
|
</th>
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Submitted"%}</th>
|
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted text-end">{% trans "Actions" %}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="divide-y divide-gray-200">
|
||||||
{% for application in applications %}
|
{% for application in applications %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50 transition">
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
<div class="fw-bold">{{ application.name }}</div>
|
<div class="font-semibold text-gray-900">{{ application.name }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
<div class="small">
|
<div class="text-sm text-gray-600 space-y-1">
|
||||||
<div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div>
|
<div class="flex items-center gap-1">
|
||||||
<div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div>
|
<i data-lucide="mail" class="w-3 h-3"></i>
|
||||||
|
{{ application.email }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<i data-lucide="phone" class="w-3 h-3"></i>
|
||||||
|
{{ application.phone }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span>
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-temple-red text-white">
|
||||||
|
{{ application.get_stage_display }}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3">
|
||||||
<div class="small text-muted">
|
<span class="text-sm text-gray-600">{{ application.created_at|date:"M d, Y" }}</span>
|
||||||
<div class="mb-1"><i class="fas fa-envelope me-2 w-20"></i>
|
|
||||||
{{application.email }}</div>
|
|
||||||
<div><i class="fas fa-phone me-2 w-20"></i>{{ application.phone }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="px-4">
|
<td class="px-4 py-3 text-right">
|
||||||
<span class="badge bg-primary-theme px-3">
|
|
||||||
{{application.get_stage_display }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4">
|
|
||||||
<span class="small text-muted">{{ application.created_at|date:"M d, Y" }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 text-end">
|
|
||||||
<a href="{% url 'application_detail' application.slug %}"
|
<a href="{% url 'application_detail' application.slug %}"
|
||||||
class="btn btn-sm btn-outline-primary" title="{% trans 'View Details' %}">
|
class="inline-flex items-center justify-center p-2 text-temple-red hover:bg-red-50 rounded-lg transition"
|
||||||
<i class="fas fa-eye"></i>
|
title="{% trans 'View Details' %}">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -289,93 +164,94 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-4">
|
<div class="text-center py-12">
|
||||||
<i class="fas fa-users fa-2x text-muted mb-3"></i>
|
<i data-lucide="users" class="w-16 h-16 text-gray-400 mx-auto mb-4"></i>
|
||||||
<h6 class="text-muted">{% trans "No applications submitted yet" %}</h6>
|
<h6 class="text-lg font-semibold text-gray-600 mb-2">{% trans "No applications submitted yet" %}</h6>
|
||||||
<p class="text-muted small">
|
<p class="text-gray-500 text-sm">
|
||||||
{% trans "Applications will appear here once the agency submits them through their portal." %}
|
{% trans "Applications will appear here once agency submits them through their portal." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<div class="col-lg-4">
|
<div class="space-y-6">
|
||||||
<!-- Progress Card -->
|
<!-- Progress Card -->
|
||||||
<div class="kaauh-card p-4 mb-4">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200 p-6">
|
||||||
<h5 class="mb-4 text-center" style="color: var(--kaauh-teal-dark);">
|
<h5 class="text-lg font-semibold text-temple-dark mb-4 text-center">
|
||||||
{% trans "Submission Progress" %}
|
{% trans "Submission Progress" %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="text-center mb-3">
|
<div class="text-center mb-4">
|
||||||
<div class="progress-ring">
|
<div class="relative inline-block">
|
||||||
<svg width="120" height="120">
|
<svg width="120" height="120" class="transform -rotate-90">
|
||||||
<circle class="progress-ring-circle"
|
<circle cx="60" cy="60" r="52"
|
||||||
stroke="#e9ecef"
|
stroke="#e5e7eb"
|
||||||
|
stroke-width="8"
|
||||||
|
fill="transparent"/>
|
||||||
|
<circle cx="60" cy="60" r="52"
|
||||||
|
stroke="#9d2235"
|
||||||
stroke-width="8"
|
stroke-width="8"
|
||||||
fill="transparent"
|
fill="transparent"
|
||||||
r="52"
|
stroke-linecap="round"
|
||||||
cx="60"
|
|
||||||
cy="60"/>
|
|
||||||
<circle class="progress-ring-circle"
|
|
||||||
stroke="var(--kaauh-teal)"
|
|
||||||
stroke-width="8"
|
|
||||||
fill="transparent"
|
|
||||||
r="52"
|
|
||||||
cx="60"
|
|
||||||
cy="60"
|
|
||||||
style="stroke-dasharray: 326.73; stroke-dashoffset: {{ stroke_dashoffset }};"/>
|
style="stroke-dasharray: 326.73; stroke-dashoffset: {{ stroke_dashoffset }};"/>
|
||||||
</svg>
|
</svg>
|
||||||
<div class="position-absolute top-50 start-50 translate-middle text-center">
|
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center">
|
||||||
<div class="h3 fw-bold mb-0 text-dark">{{ total_applications }}</div>
|
<div class="text-3xl font-bold text-gray-900">{{ total_applications }}</div>
|
||||||
<div class="small text-muted text-uppercase">{% trans "of" %} {{ assignment.max_candidates}}</div>
|
<div class="text-xs text-gray-500 uppercase">{% trans "of" %} {{ assignment.max_candidates }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center mb-4">
|
||||||
<div class="h4 mb-1">{{ total_applications }}</div>
|
<div class="text-4xl font-bold text-temple-red mb-1">{{ total_applications }}</div>
|
||||||
<div class="text-muted">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
|
<div class="text-gray-600">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="progress mt-3 " style="height: 8px;">
|
|
||||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||||
<div class="progress-bar bg-primary-theme" style="width: {{ progress }}%"></div>
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||||
|
<div class="bg-temple-red h-2 rounded-full transition-all duration-500" style="width: {{ progress }}%"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Actions Card -->
|
<!-- Actions Card -->
|
||||||
<div class="kaauh-card p-4">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200 p-6">
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
<h5 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||||
<i class="fas fa-cog me-2"></i>
|
<i data-lucide="settings" class="w-5 h-5"></i>
|
||||||
{% trans "Actions" %}
|
{% trans "Actions" %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="d-grid gap-2">
|
<div class="space-y-2">
|
||||||
<a href="{% url "message_list" %}"
|
<a href="{% url "message_list" %}"
|
||||||
class="btn btn-outline-primary">
|
class="w-full inline-flex items-center justify-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg text-sm font-medium transition">
|
||||||
<i class="fas fa-envelope me-1"></i> {% trans "Send Message" %}
|
<i data-lucide="mail" class="w-4 h-4"></i>
|
||||||
|
{% trans "Send Message" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% if assignment.is_active and not assignment.is_expired %}
|
{% if assignment.is_active and not assignment.is_expired %}
|
||||||
<button type="button" class="btn btn-outline-warning"
|
<button type="button"
|
||||||
data-bs-toggle="modal" data-bs-target="#extendDeadlineModal">
|
class="w-full inline-flex items-center justify-center gap-2 border border-yellow-500 text-yellow-700 hover:bg-yellow-50 px-4 py-2.5 rounded-lg text-sm font-medium transition"
|
||||||
<i class="fas fa-clock me-1"></i> {% trans "Extend Deadline" %}
|
onclick="document.getElementById('extendDeadlineModal').classList.remove('hidden')">
|
||||||
|
<i data-lucide="clock" class="w-4 h-4"></i>
|
||||||
|
{% trans "Extend Deadline" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if assignment.is_active and assignment.status == 'ACTIVE' %}
|
{% if assignment.is_active and assignment.status == 'ACTIVE' %}
|
||||||
<button type="button" class="btn btn-danger"
|
<button type="button"
|
||||||
data-bs-toggle="modal" data-bs-target="#cancelAssignmentModal">
|
class="w-full inline-flex items-center justify-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2.5 rounded-lg text-sm font-medium transition"
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel Assignment" %}
|
onclick="document.getElementById('cancelAssignmentModal').classList.remove('hidden')">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Cancel Assignment" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<a href="{% url 'agency_assignment_update' assignment.slug %}"
|
<a href="{% url 'agency_assignment_update' assignment.slug %}"
|
||||||
class="btn btn-outline-secondary">
|
class="w-full inline-flex items-center justify-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg text-sm font-medium transition">
|
||||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Assignment" %}
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
|
{% trans "Edit Assignment" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -384,35 +260,35 @@
|
|||||||
|
|
||||||
<!-- Messages Section -->
|
<!-- Messages Section -->
|
||||||
{% if messages_ %}
|
{% if messages_ %}
|
||||||
<div class="kaauh-card p-4 mt-4">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200 p-6 mt-6">
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
<h5 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||||
<i class="fas fa-comments me-2"></i>
|
<i data-lucide="message-square" class="w-5 h-5"></i>
|
||||||
{% trans "Recent Messages" %}
|
{% trans "Recent Messages" %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{% for message in messages_|slice:":6" %}
|
{% for message in messages_|slice:":6" %}
|
||||||
<div class="col-lg-6 mb-3">
|
<div class="border-l-4 {% if not message.is_read %}border-blue-500 bg-blue-50{% else %}border-temple-red bg-gray-50{% endif %} rounded-r-lg p-4">
|
||||||
<div class="message-item {% if not message.is_read %}unread{% endif %}">
|
<div class="flex justify-between items-start mb-2">
|
||||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
<div class="font-semibold text-gray-900">{{ message.subject }}</div>
|
||||||
<div class="fw-bold">{{ message.subject }}</div>
|
<span class="text-sm text-gray-500">{{ message.created_at|date:"Y-m-d H:i" }}</span>
|
||||||
<small class="text-muted">{{ message.created_at|date:"Y-m-d H:i" }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="small text-muted mb-2">
|
<div class="text-sm text-gray-600 mb-2">
|
||||||
{% trans "From" %}: {{ message.sender.get_full_name }}
|
{% trans "From" %}: {{ message.sender.get_full_name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="small">{{ message.message|truncatewords:30 }}</div>
|
<div class="text-sm text-gray-700">{{ message.message|truncatewords:30 }}</div>
|
||||||
{% if not message.is_read %}
|
{% if not message.is_read %}
|
||||||
<span class="badge bg-info mt-2">{% trans "New" %}</span>
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mt-2">
|
||||||
|
{% trans "New" %}
|
||||||
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if messages_.count > 6 %}
|
{% if messages_.count > 6 %}
|
||||||
<div class="text-center mt-3">
|
<div class="text-center mt-4">
|
||||||
<a href="#" class="btn btn-outline-primary btn-sm">
|
<a href="#" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
{% trans "View All Messages" %}
|
{% trans "View All Messages" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -422,55 +298,61 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Cancel Assignment Modal -->
|
<!-- Cancel Assignment Modal -->
|
||||||
<div class="modal fade" id="cancelAssignmentModal" tabindex="-1">
|
<div id="cancelAssignmentModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
|
||||||
<div class="modal-dialog">
|
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||||
<div class="modal-content">
|
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('cancelAssignmentModal').classList.add('hidden')"></div>
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full">
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
{% trans "Cancel Assignment" %}
|
<div class="sm:flex sm:items-start">
|
||||||
</h5>
|
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
<i data-lucide="alert-triangle" class="w-6 h-6 text-red-600"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
<div class="alert alert-warning mb-3">
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
{% trans "Cancel Assignment" %}
|
||||||
|
</h3>
|
||||||
|
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-4">
|
||||||
|
<p class="text-sm text-yellow-800">
|
||||||
<strong>{% trans "Warning:" %}</strong>
|
<strong>{% trans "Warning:" %}</strong>
|
||||||
{% trans "This action cannot be undone. The agency will no longer be able to submit applications." %}
|
{% trans "This action cannot be undone. The agency will no longer be able to submit applications." %}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card mb-3">
|
|
||||||
<div class="card-body bg-light">
|
|
||||||
<h6 class="card-title text-primary mb-0">
|
|
||||||
<i class="fas fa-building me-2"></i>
|
|
||||||
{{ assignment.agency.name }}
|
|
||||||
</h6>
|
|
||||||
<p class="card-text text-muted mb-0">
|
|
||||||
<i class="fas fa-briefcase me-2"></i>
|
|
||||||
{{ assignment.job.title }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||||
|
<h6 class="text-temple-red font-semibold mb-2 flex items-center gap-2">
|
||||||
|
<i data-lucide="building" class="w-4 h-4"></i>
|
||||||
|
{{ assignment.agency.name }}
|
||||||
|
</h6>
|
||||||
|
<p class="text-gray-600 text-sm flex items-center gap-2">
|
||||||
|
<i data-lucide="briefcase" class="w-4 h-4"></i>
|
||||||
|
{{ assignment.job.title }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" action="{% url 'agency_assignment_cancel' assignment.slug %}">
|
<form method="post" action="{% url 'agency_assignment_cancel' assignment.slug %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="mb-3">
|
<div class="mb-4">
|
||||||
<label for="cancel_reason" class="form-label fw-bold">
|
<label for="cancel_reason" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
<i class="fas fa-comment-alt me-2"></i>
|
<i data-lucide="message-square" class="w-4 h-4 inline mr-1"></i>
|
||||||
{% trans "Cancellation Reason" %}
|
{% trans "Cancellation Reason" %}
|
||||||
<span class="text-muted fw-normal">({% trans "Optional" %})</span>
|
<span class="font-normal text-gray-500">({% trans "Optional" %})</span>
|
||||||
</label>
|
</label>
|
||||||
<textarea class="form-control" id="cancel_reason" name="cancel_reason" rows="4"
|
<textarea class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900"
|
||||||
|
id="cancel_reason" name="cancel_reason" rows="4"
|
||||||
placeholder="{% trans 'Enter reason for cancelling this assignment (optional)...' %}"></textarea>
|
placeholder="{% trans 'Enter reason for cancelling this assignment (optional)...' %}"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="flex justify-end gap-3">
|
||||||
<a href="{% url 'agency_assignment_detail' assignment.slug %}" class="btn btn-secondary">
|
<button type="button"
|
||||||
<i class="fas fa-arrow-left me-1"></i>
|
class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition"
|
||||||
|
onclick="document.getElementById('cancelAssignmentModal').classList.add('hidden')">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</a>
|
</button>
|
||||||
<button type="submit" class="btn btn-danger">
|
<button type="submit"
|
||||||
<i class="fas fa-times-circle me-1"></i>
|
class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="x-circle" class="w-4 h-4"></i>
|
||||||
{% trans "Cancel Assignment" %}
|
{% trans "Cancel Assignment" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -479,107 +361,60 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Extend Deadline Modal -->
|
<!-- Extend Deadline Modal -->
|
||||||
<div class="modal fade" id="extendDeadlineModal" tabindex="-1">
|
<div id="extendDeadlineModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
|
||||||
<div class="modal-dialog">
|
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||||
<div class="modal-content">
|
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('extendDeadlineModal').classList.add('hidden')"></div>
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full">
|
||||||
<i class="fas fa-clock me-2"></i>
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<i data-lucide="clock" class="w-5 h-5"></i>
|
||||||
{% trans "Extend Assignment Deadline" %}
|
{% trans "Extend Assignment Deadline" %}
|
||||||
</h5>
|
</h3>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="{% url 'agency_assignment_extend_deadline' assignment.slug %}">
|
<form method="post" action="{% url 'agency_assignment_extend_deadline' assignment.slug %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="modal-body">
|
<div class="mb-4">
|
||||||
<div class="mb-3">
|
<label for="new_deadline" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
<label for="new_deadline" class="form-label">
|
{% trans "New Deadline" %} <span class="text-red-600">*</span>
|
||||||
{% trans "New Deadline" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
</label>
|
||||||
<input type="datetime-local" class="form-control" id="new_deadline"
|
<input type="datetime-local"
|
||||||
name="new_deadline" required>
|
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900"
|
||||||
<small class="form-text text-muted">
|
id="new_deadline" name="new_deadline" required>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
{% trans "Current deadline:" %} {{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
{% trans "Current deadline:" %} {{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
||||||
</small>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
<div class="flex justify-end gap-3">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
<button type="button"
|
||||||
|
class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition"
|
||||||
|
onclick="document.getElementById('extendDeadlineModal').classList.add('hidden')">
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" class="btn btn-main-action">
|
<button type="submit"
|
||||||
<i class="fas fa-clock me-1"></i> {% trans "Extend Deadline" %}
|
class="inline-flex items-center gap-2 bg-temple-red hover:bg-red-800 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="clock" class="w-4 h-4"></i>
|
||||||
|
{% trans "Extend Deadline" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
function copyToClipboard(text) {
|
|
||||||
navigator.clipboard.writeText(text).then(function() {
|
|
||||||
// Show success message
|
|
||||||
const toast = document.createElement('div');
|
|
||||||
toast.className = 'position-fixed top-0 end-0 p-3';
|
|
||||||
toast.style.zIndex = '1050';
|
|
||||||
toast.innerHTML = `
|
|
||||||
<div class="toast show" role="alert">
|
|
||||||
<div class="toast-header">
|
|
||||||
<strong class="me-auto">{% trans "Success" %}</strong>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
|
|
||||||
</div>
|
|
||||||
<div class="toast-body">
|
|
||||||
{% trans "Token copied to clipboard!" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
document.body.appendChild(toast);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
toast.remove();
|
|
||||||
}, 3000);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyToClipboard(elementId) {
|
|
||||||
const element = document.getElementById(elementId);
|
|
||||||
element.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
|
|
||||||
// Show feedback
|
|
||||||
const button = element.nextElementSibling;
|
|
||||||
const originalHTML = button.innerHTML;
|
|
||||||
button.innerHTML = '<i class="fas fa-check"></i>';
|
|
||||||
button.classList.add('btn-success');
|
|
||||||
button.classList.remove('btn-outline-secondary');
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
button.innerHTML = originalHTML;
|
|
||||||
button.classList.remove('btn-success');
|
|
||||||
button.classList.add('btn-outline-secondary');
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmDeactivate() {
|
|
||||||
if (confirm('{% trans "Are you sure you want to deactivate this access link? Agencies will no longer be able to use it." %}')) {
|
|
||||||
// Submit form to deactivate
|
|
||||||
window.location.href = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmReactivate() {
|
|
||||||
if (confirm('{% trans "Are you sure you want to reactivate this access link?" %}')) {
|
|
||||||
// Submit form to reactivate
|
|
||||||
window.location.href = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize Lucide icons
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
// Set minimum datetime for new deadline
|
// Set minimum datetime for new deadline
|
||||||
const deadlineInput = document.getElementById('new_deadline');
|
const deadlineInput = document.getElementById('new_deadline');
|
||||||
if (deadlineInput) {
|
if (deadlineInput) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,431 +1,385 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n widget_tweaks %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{{ title }} - {{ block.super }}{% endblock %}
|
{% block title %}{{ title }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* UI Variables for the KAAT-S Theme */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-gray-light: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Container Styling */
|
|
||||||
.form-container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card Styling */
|
|
||||||
.card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Action Button Style */
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.4rem;
|
|
||||||
padding: 0.5rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary Button Style */
|
|
||||||
.btn-outline-secondary {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Field Styling */
|
|
||||||
.form-control:focus {
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-select:focus {
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Breadcrumb Styling */
|
|
||||||
.breadcrumb {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-item + .breadcrumb-item::before {
|
|
||||||
content: ">";
|
|
||||||
color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Alert Styling */
|
|
||||||
.alert {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading State */
|
|
||||||
.btn.loading {
|
|
||||||
position: relative;
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.loading::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin: auto;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
border-top-color: #ffffff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Current Profile Section */
|
|
||||||
.current-profile {
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-profile h6 {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="space-y-6">
|
||||||
<div class="form-container">
|
|
||||||
<!-- Breadcrumb Navigation -->
|
<!-- Header Card with Gradient Background -->
|
||||||
<nav aria-label="breadcrumb">
|
<div class="bg-gradient-to-r from-temple-red to-[#7a1a29] rounded-2xl shadow-lg p-6 md:p-8 text-white">
|
||||||
<ol class="breadcrumb">
|
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||||
<li class="breadcrumb-item">
|
<div class="flex-1">
|
||||||
<a href="{% url 'agency_list' %}" class="text-decoration-none text-secondary">
|
<h1 class="text-2xl md:text-3xl font-bold mb-2 flex items-center gap-3">
|
||||||
<i class="fas fa-building me-1"></i> {% trans "Agencies" %}
|
<i data-lucide="building" class="w-8 h-8"></i>
|
||||||
</a>
|
{{ title }}
|
||||||
</li>
|
</h1>
|
||||||
|
<p class="text-white/80 text-sm md:text-base">
|
||||||
{% if agency %}
|
{% if agency %}
|
||||||
<li class="breadcrumb-item">
|
{% trans "Update agency information" %}
|
||||||
<a href="{% url 'agency_detail' agency.slug %}" class="text-decoration-none text-secondary">
|
|
||||||
{{ agency.name }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="breadcrumb-item active" aria-current="page"
|
|
||||||
style="
|
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;">{% trans "Update" %}</li>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="breadcrumb-item active" aria-current="page"
|
{% trans "Enter details to create a new agency." %}
|
||||||
style="
|
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;">{% trans "Create" %}</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ol>
|
</p>
|
||||||
</nav>
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{% if agency %}
|
||||||
|
<a href="{% url 'agency_detail' agency.slug %}"
|
||||||
|
class="inline-flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg text-white font-medium text-sm transition backdrop-blur-sm touch-target">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
|
<span class="hidden sm:inline">{% trans "View Details" %}</span>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'agency_delete' agency.slug %}"
|
||||||
|
class="inline-flex items-center gap-2 px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg text-white font-medium text-sm transition touch-target">
|
||||||
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
|
<span class="hidden sm:inline">{% trans "Delete" %}</span>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'agency_list' %}"
|
||||||
|
class="inline-flex items-center gap-2 px-4 py-2 bg-white/10 hover:bg-white/20 rounded-lg text-white font-medium text-sm transition backdrop-blur-sm touch-target">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
<span class="hidden sm:inline">{% trans "Back to List" %}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Header -->
|
{% if agency %}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<!-- Current Agency Info Card -->
|
||||||
<h6 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||||
<i class="fas fa-building me-2"></i> {{ title }}
|
<div class="bg-temple-cream rounded-lg p-4 border-l-4 border-temple-red">
|
||||||
|
<h6 class="text-temple-dark font-bold mb-3 flex items-center gap-2">
|
||||||
|
<i data-lucide="info" class="w-4 h-4"></i>
|
||||||
|
{% trans "Currently Editing" %}
|
||||||
</h6>
|
</h6>
|
||||||
<div class="d-flex gap-2">
|
<div class="space-y-2">
|
||||||
{% if agency %}
|
<h5 class="text-lg font-semibold text-gray-900">{{ agency.name }}</h5>
|
||||||
<a href="{% url 'agency_detail' agency.slug %}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'agency_delete' agency.slug %}" class="btn btn-danger">
|
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'agency_list' %}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if agency %}
|
|
||||||
<!-- Current Agency Info -->
|
|
||||||
<div class="card shadow-sm mb-4">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="current-profile">
|
|
||||||
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h5 class="mb-1">{{ agency.name }}</h5>
|
|
||||||
{% if agency.contact_person %}
|
{% if agency.contact_person %}
|
||||||
<p class="text-muted mb-0">{% trans "Contact" %}: {{ agency.contact_person }}</p>
|
<p class="text-gray-600 text-sm">{% trans "Contact" %}: {{ agency.contact_person }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if agency.email %}
|
{% if agency.email %}
|
||||||
<p class="text-muted mb-0">{{ agency.email }}</p>
|
<p class="text-gray-600 text-sm">{{ agency.email }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<small class="text-muted">
|
<p class="text-gray-500 text-xs">
|
||||||
{% trans "Created" %}: {{ agency.created_at|date:"d M Y" }} •
|
{% trans "Created" %}: {{ agency.created_at|date:"d M Y" }} •
|
||||||
{% trans "Last Updated" %}: {{ agency.updated_at|date:"d M Y" }}
|
{% trans "Last Updated" %}: {{ agency.updated_at|date:"d M Y" }}
|
||||||
</small>
|
</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Form Card -->
|
<!-- Form Card -->
|
||||||
<div class="card shadow-sm">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
|
||||||
<div class="card-body p-4">
|
<div class="border-b border-gray-200 px-6 py-4 bg-gray-50 rounded-t-xl">
|
||||||
|
<h2 class="text-lg font-bold text-temple-red flex items-center gap-2">
|
||||||
|
<i data-lucide="file-text" class="w-5 h-5"></i>
|
||||||
|
{% trans "Agency Information" %}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-6 md:p-8">
|
||||||
{% if form.non_field_errors %}
|
{% if form.non_field_errors %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="bg-red-50 border border-red-200 rounded-xl p-4 mb-6" role="alert">
|
||||||
<h5 class="alert-heading">
|
<div class="flex items-start gap-3">
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
<i data-lucide="alert-circle" class="w-5 h-5 text-red-600 shrink-0 mt-0.5"></i>
|
||||||
</h5>
|
<div>
|
||||||
|
<h5 class="font-bold text-red-800 mb-1">{% trans "Error" %}</h5>
|
||||||
{% for error in form.non_field_errors %}
|
{% for error in form.non_field_errors %}
|
||||||
<p class="mb-0">{{ error }}</p>
|
<p class="text-red-700 text-sm">{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if messages %}
|
<form method="post" novalidate id="agency-form" class="space-y-6">
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
|
||||||
{{ message }}
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<form method="post" novalidate id="agency-form">
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<!-- Two Column Form Layout -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<!-- Name -->
|
<!-- Name -->
|
||||||
<div class="mb-3">
|
<div class="space-y-2">
|
||||||
<label for="{{ form.name.id_for_label }}" class="form-label">
|
<label for="{{ form.name.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
{{ form.name.label }} <span class="text-danger">*</span>
|
{{ form.name.label }} <span class="text-red-600">*</span>
|
||||||
</label>
|
</label>
|
||||||
{{ form.name|add_class:"form-control" }}
|
<input type="text"
|
||||||
|
name="name"
|
||||||
|
id="{{ form.name.id_for_label }}"
|
||||||
|
value="{{ form.name.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Agency Name' %}"
|
||||||
|
{% if form.name.field.required %}required{% endif %}>
|
||||||
{% if form.name.errors %}
|
{% if form.name.errors %}
|
||||||
{% for error in form.name.errors %}
|
{% for error in form.name.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.name.help_text %}
|
{% if form.name.help_text %}
|
||||||
<div class="form-text">{{ form.name.help_text }}</div>
|
<p class="text-gray-500 text-xs">{{ form.name.help_text }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Contact Person and Phone -->
|
<!-- Contact Person -->
|
||||||
<div class="row">
|
<div class="space-y-2">
|
||||||
<div class="col-md-6 mb-3">
|
<label for="{{ form.contact_person.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
<label for="{{ form.contact_person.id_for_label }}" class="form-label">
|
|
||||||
{{ form.contact_person.label }}
|
{{ form.contact_person.label }}
|
||||||
</label>
|
</label>
|
||||||
{{ form.contact_person|add_class:"form-control" }}
|
<input type="text"
|
||||||
|
name="contact_person"
|
||||||
|
id="{{ form.contact_person.id_for_label }}"
|
||||||
|
value="{{ form.contact_person.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Contact Person' %}">
|
||||||
{% if form.contact_person.errors %}
|
{% if form.contact_person.errors %}
|
||||||
{% for error in form.contact_person.errors %}
|
{% for error in form.contact_person.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.contact_person.help_text %}
|
{% if form.contact_person.help_text %}
|
||||||
<div class="form-text">{{ form.contact_person.help_text }}</div>
|
<p class="text-gray-500 text-xs">{{ form.contact_person.help_text }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<!-- Phone -->
|
||||||
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.phone.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
{{ form.phone.label }}
|
{{ form.phone.label }}
|
||||||
</label>
|
</label>
|
||||||
{{ form.phone|add_class:"form-control" }}
|
<input type="tel"
|
||||||
|
name="phone"
|
||||||
|
id="{{ form.phone.id_for_label }}"
|
||||||
|
value="{{ form.phone.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Phone Number' %}">
|
||||||
{% if form.phone.errors %}
|
{% if form.phone.errors %}
|
||||||
{% for error in form.phone.errors %}
|
{% for error in form.phone.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.phone.help_text %}
|
{% if form.phone.help_text %}
|
||||||
<div class="form-text">{{ form.phone.help_text }}</div>
|
<p class="text-gray-500 text-xs">{{ form.phone.help_text }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Email and Website -->
|
<!-- Email -->
|
||||||
<div class="row">
|
<div class="space-y-2">
|
||||||
<div class="col-md-6 mb-3">
|
<label for="{{ form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
<label for="{{ form.email.id_for_label }}" class="form-label">
|
{{ form.email.label }} <span class="text-red-600">*</span>
|
||||||
{{ form.email.label }}<span class="text-danger">*</span>
|
|
||||||
</label>
|
</label>
|
||||||
{{ form.email|add_class:"form-control" }}
|
<input type="email"
|
||||||
|
name="email"
|
||||||
|
id="{{ form.email.id_for_label }}"
|
||||||
|
value="{{ form.email.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Email Address' %}"
|
||||||
|
{% if form.email.field.required %}required{% endif %}>
|
||||||
{% if form.email.errors %}
|
{% if form.email.errors %}
|
||||||
{% for error in form.email.errors %}
|
{% for error in form.email.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.email.help_text %}
|
{% if form.email.help_text %}
|
||||||
<div class="form-text">{{ form.email.help_text }}</div>
|
<p class="text-gray-500 text-xs">{{ form.email.help_text }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<!-- Website -->
|
||||||
<label for="{{ form.website.id_for_label }}" class="form-label">
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.website.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
{{ form.website.label }}
|
{{ form.website.label }}
|
||||||
</label>
|
</label>
|
||||||
{{ form.website|add_class:"form-control" }}
|
<input type="url"
|
||||||
|
name="website"
|
||||||
|
id="{{ form.website.id_for_label }}"
|
||||||
|
value="{{ form.website.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Website URL' %}">
|
||||||
{% if form.website.errors %}
|
{% if form.website.errors %}
|
||||||
{% for error in form.website.errors %}
|
{% for error in form.website.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.website.help_text %}
|
{% if form.website.help_text %}
|
||||||
<div class="form-text">{{ form.website.help_text }}</div>
|
<p class="text-gray-500 text-xs">{{ form.website.help_text }}</p>
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Address -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.address.id_for_label }}" class="form-label">
|
|
||||||
{{ form.address.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.address|add_class:"form-control" }}
|
|
||||||
{% if form.address.errors %}
|
|
||||||
{% for error in form.address.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.address.help_text %}
|
|
||||||
<div class="form-text">{{ form.address.help_text }}</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Country and City -->
|
<!-- Country -->
|
||||||
<div class="row">
|
<div class="space-y-2">
|
||||||
<div class="col-md-6 mb-3">
|
<label for="{{ form.country.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
<label for="{{ form.country.id_for_label }}" class="form-label">
|
|
||||||
{{ form.country.label }}
|
{{ form.country.label }}
|
||||||
</label>
|
</label>
|
||||||
{{ form.country|add_class:"form-control" }}
|
<input type="text"
|
||||||
|
name="country"
|
||||||
|
id="{{ form.country.id_for_label }}"
|
||||||
|
value="{{ form.country.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Country' %}">
|
||||||
{% if form.country.errors %}
|
{% if form.country.errors %}
|
||||||
{% for error in form.country.errors %}
|
{% for error in form.country.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.country.help_text %}
|
{% if form.country.help_text %}
|
||||||
<div class="form-text">{{ form.country.help_text }}</div>
|
<p class="text-gray-500 text-xs">{{ form.country.help_text }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<!-- City -->
|
||||||
<label for="{{ form.city.id_for_label }}" class="form-label">
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.city.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
{{ form.city.label }}
|
{{ form.city.label }}
|
||||||
</label>
|
</label>
|
||||||
{{ form.city|add_class:"form-control" }}
|
<input type="text"
|
||||||
|
name="city"
|
||||||
|
id="{{ form.city.id_for_label }}"
|
||||||
|
value="{{ form.city.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'City' %}">
|
||||||
{% if form.city.errors %}
|
{% if form.city.errors %}
|
||||||
{% for error in form.city.errors %}
|
{% for error in form.city.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.city.help_text %}
|
{% if form.city.help_text %}
|
||||||
<div class="form-text">{{ form.city.help_text }}</div>
|
<p class="text-gray-500 text-xs">{{ form.city.help_text }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Address (Full Width) -->
|
||||||
<div class="mb-4">
|
<div class="space-y-2">
|
||||||
<label for="{{ form.description.id_for_label }}" class="form-label">
|
<label for="{{ form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.address.label }}
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
name="address"
|
||||||
|
id="{{ form.address.id_for_label }}"
|
||||||
|
rows="3"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm resize-none"
|
||||||
|
placeholder="{% trans 'Street Address' %}">{{ form.address.value|default:'' }}</textarea>
|
||||||
|
{% if form.address.errors %}
|
||||||
|
{% for error in form.address.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.address.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.address.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Description (Full Width) -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.description.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
{{ form.description.label }}
|
{{ form.description.label }}
|
||||||
</label>
|
</label>
|
||||||
{{ form.description|add_class:"form-control" }}
|
<textarea
|
||||||
|
name="description"
|
||||||
|
id="{{ form.description.id_for_label }}"
|
||||||
|
rows="4"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm resize-none"
|
||||||
|
placeholder="{% trans 'Agency Description' %}">{{ form.description.value|default:'' }}</textarea>
|
||||||
{% if form.description.errors %}
|
{% if form.description.errors %}
|
||||||
{% for error in form.description.errors %}
|
{% for error in form.description.errors %}
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if form.description.help_text %}
|
{% if form.description.help_text %}
|
||||||
<div class="form-text">{{ form.description.help_text }}</div>
|
<p class="text-gray-500 text-xs">{{ form.description.help_text }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<!-- Submit Button -->
|
||||||
<button form="agency-form" type="submit" class="btn btn-main-action">
|
<div class="pt-4 border-t border-gray-200">
|
||||||
<i class="fas fa-save me-1"></i> {{ button_text }}
|
<button type="submit"
|
||||||
|
class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-8 py-3 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold rounded-lg text-sm transition shadow-md hover:shadow-lg transform hover:-translate-y-0.5 touch-target">
|
||||||
|
<i data-lucide="save" class="w-4 h-4"></i>
|
||||||
|
{{ button_text }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Reinitialize Lucide icons for dynamically added content
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
// Form Validation
|
// Form Validation
|
||||||
const form = document.getElementById('agency-form');
|
const form = document.getElementById('agency-form');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener('submit', function(e) {
|
||||||
const submitBtn = form.querySelector('button[type="submit"]');
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
submitBtn.classList.add('loading');
|
submitBtn.classList.add('opacity-75', 'cursor-not-allowed');
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
|
submitBtn.innerHTML = `
|
||||||
|
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||||
|
</svg>
|
||||||
|
{% trans 'Saving...' %}
|
||||||
|
`;
|
||||||
|
|
||||||
// Basic validation
|
// Basic validation
|
||||||
const name = document.getElementById('id_name');
|
const name = document.getElementById('{{ form.name.id_for_label }}');
|
||||||
if (name && !name.value.trim()) {
|
if (name && !name.value.trim()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitBtn.classList.remove('loading');
|
submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`;
|
||||||
|
lucide.createIcons();
|
||||||
alert('{% trans "Agency name is required." %}');
|
alert('{% trans "Agency name is required." %}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const email = document.getElementById('id_email');
|
const email = document.getElementById('{{ form.email.id_for_label }}');
|
||||||
if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
|
if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitBtn.classList.remove('loading');
|
submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`;
|
||||||
|
lucide.createIcons();
|
||||||
alert('{% trans "Please enter a valid email address." %}');
|
alert('{% trans "Please enter a valid email address." %}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const website = document.getElementById('id_website');
|
const website = document.getElementById('{{ form.website.id_for_label }}');
|
||||||
if (website && website.value.trim() && !isValidURL(website.value.trim())) {
|
if (website && website.value.trim() && !isValidURL(website.value.trim())) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitBtn.classList.remove('loading');
|
submitBtn.classList.remove('opacity-75', 'cursor-not-allowed');
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-4 h-4"></i> {{ button_text }}`;
|
||||||
|
lucide.createIcons();
|
||||||
alert('{% trans "Please enter a valid website URL." %}');
|
alert('{% trans "Please enter a valid website URL." %}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,269 +3,141 @@
|
|||||||
|
|
||||||
{% block title %}{{ assignment.job.title }} - {{ assignment.agency.name }} - {% trans "Agency Portal" %}{% endblock %}
|
{% block title %}{{ assignment.job.title }} - {{ assignment.agency.name }} - {% trans "Agency Portal" %}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* KAAT-S UI Variables */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-success: #28a745;
|
|
||||||
--kaauh-info: #17a2b8;
|
|
||||||
--kaauh-danger: #dc3545;
|
|
||||||
--kaauh-warning: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kaauh-card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.3em 0.7em;
|
|
||||||
border-radius: 0.35rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.status-ACTIVE { background-color: var(--kaauh-teal-dark); color: white; }
|
|
||||||
.status-EXPIRED { background-color: var(--kaauh-teal-dark); color: white; }
|
|
||||||
.status-COMPLETED { background-color: var(--kaauh-teal-dark); color: white; }
|
|
||||||
.status-CANCELLED { background-color: var(--kaauh-teal-dark); color: white; }
|
|
||||||
|
|
||||||
.progress-ring {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-ring-circle {
|
|
||||||
transition: stroke-dashoffset 0.35s;
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
transform-origin: 50% 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-ring-text {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.candidate-item {
|
|
||||||
border-left: 4px solid var(--kaauh-teal);
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border-radius: 0 0.5rem 0.5rem 0;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.candidate-item:hover {
|
|
||||||
background-color: #e9ecef;
|
|
||||||
transform: translateX(2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-item {
|
|
||||||
border-left: 4px solid var(--kaauh-teal);
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border-radius: 0 0.5rem 0.5rem 0;
|
|
||||||
}
|
|
||||||
.message-item.unread {
|
|
||||||
border-left-color: var(--kaauh-info);
|
|
||||||
background-color: #e7f3ff;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 class="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
|
||||||
<i class="fas fa-briefcase me-2"></i>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="briefcase" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
{{ assignment.job.title }}
|
{{ assignment.job.title }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">
|
<p class="text-gray-600">{% trans "Assignment Details" %} - {{ assignment.agency.name }}</p>
|
||||||
{% trans "Assignment Details" %} - {{ assignment.agency.name }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="flex gap-2">
|
||||||
<a href="{% url 'agency_portal_dashboard' %}" class="btn btn-outline-secondary btn-sm me-2">
|
<a href="{% url 'agency_portal_dashboard' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl transition">
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Dashboard" %}
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Dashboard" %}
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="btn btn-sm btn-main-action {% if assignment.is_full %}disabled{% endif %}" >
|
<a href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition shadow-sm hover:shadow-md {% if assignment.is_full %}opacity-50 cursor-not-allowed{% endif %}">
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit New application" %}
|
<i data-lucide="user-plus" class="w-4 h-4"></i> {% trans "Submit New application" %}
|
||||||
</a>
|
</a>
|
||||||
{% comment %} <a href="#" class="btn btn-outline-info">
|
|
||||||
<i class="fas fa-envelope me-1"></i> {% trans "Messages" %}
|
|
||||||
{% if total_unread_messages > 0 %}
|
|
||||||
<span class="badge bg-danger ms-1">{{ total_unread_messages }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</a> {% endcomment %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
<!-- Assignment Overview -->
|
<!-- Main Content -->
|
||||||
<div class="col-lg-8">
|
<div class="lg:col-span-2 space-y-6">
|
||||||
<!-- Assignment Details Card -->
|
<!-- Assignment Details Card -->
|
||||||
<div class="kaauh-card p-4 mb-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
<div class="p-6">
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
<h5 class="text-xl font-bold text-gray-900 mb-6 flex items-center gap-2">
|
||||||
|
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="info" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
{% trans "Assignment Details" %}
|
{% trans "Assignment Details" %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="text-muted small">{% trans "Job Title" %}</label>
|
|
||||||
<div class="fw-bold">{{ assignment.job.title }}</div>
|
|
||||||
<div class="text-muted small">{{ assignment.job.department }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="text-muted small">{% trans "Status" %}</label>
|
|
||||||
<div>
|
<div>
|
||||||
<span class="status-badge status-{{ assignment.status }}" >
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Job Title" %}</label>
|
||||||
|
<div class="font-bold text-gray-900">{{ assignment.job.title }}</div>
|
||||||
|
<div class="text-sm text-gray-600">{{ assignment.job.department }}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Status" %}</label>
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
||||||
{{ assignment.get_status_display }}
|
{{ assignment.get_status_display }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
</div>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Deadline" %}</label>
|
||||||
<div class="col-md-6">
|
<div class="{% if assignment.is_expired %}text-red-600{% else %}text-gray-700{% endif %}">
|
||||||
<div class="mb-3">
|
<i data-lucide="calendar" class="w-4 h-4 inline mr-1"></i>
|
||||||
<label class="text-muted small">{% trans "Deadline" %}</label>
|
|
||||||
<div class="{% if assignment.is_expired %}text-danger{% else %}text-muted{% endif %}">
|
|
||||||
<i class="fas fa-calendar-alt me-1"></i>
|
|
||||||
{{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
{{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
||||||
</div>
|
</div>
|
||||||
{% if assignment.is_expired %}
|
{% if assignment.is_expired %}
|
||||||
<small class="text-danger">
|
<div class="text-sm text-red-600 mt-1">
|
||||||
<i class="fas fa-exclamation-triangle me-1"></i>{% trans "Expired" %}
|
<i data-lucide="alert-triangle" class="w-3 h-3 inline mr-1"></i>{% trans "Expired" %}
|
||||||
</small>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<small class="text-primary-theme">
|
<div class="text-sm text-blue-600 mt-1">
|
||||||
<i class="fas fa-clock me-1"></i>{{ assignment.days_remaining }} {% trans "days remaining" %}
|
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>{{ assignment.days_remaining }} {% trans "days remaining" %}
|
||||||
</small>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div>
|
||||||
<label class="text-muted small">{% trans "Maximum applications" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Maximum applications" %}</label>
|
||||||
<div class="fw-bold">{{max_applications }} {% trans "applications" %}</div>
|
<div class="font-bold text-gray-900">{{max_applications }} {% trans "applications" %}</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if assignment.job.description %}
|
{% if assignment.job.description %}
|
||||||
<div class="mt-3 pt-3 border-top">
|
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||||
<label class="text-muted small">{% trans "Job Description " %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-2">{% trans "Job Description" %}</label>
|
||||||
<div class="text-muted">
|
<div class="text-gray-700">{{ assignment.job.description|safe|truncatewords:50 }}</div>
|
||||||
{{ assignment.job.description|safe|truncatewords:50 }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Quick Actions Card -->
|
|
||||||
{% comment %} <div class="kaauh-card p-4 mb-4">
|
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
|
||||||
<i class="fas fa-bolt me-2"></i>
|
|
||||||
{% trans "Quick Actions" %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="d-grid gap-2">
|
|
||||||
{% if assignment.can_submit %}
|
|
||||||
<a href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit New application" %}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<button class="btn btn-outline-secondary" disabled>
|
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Cannot Submit applications" %}
|
|
||||||
</button>
|
|
||||||
<div class="alert alert-warning mt-2">
|
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
||||||
{% if assignment.is_expired %}
|
|
||||||
{% trans "This assignment has expired. Submissions are no longer accepted." %}
|
|
||||||
{% elif assignment.is_full %}
|
|
||||||
{% trans "Maximum application limit reached for this assignment." %}
|
|
||||||
{% else %}
|
|
||||||
{% trans "This assignment is not currently active." %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'agency_portal_dashboard' %}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-dashboard me-1"></i> {% trans "Dashboard" %}
|
|
||||||
</a>
|
|
||||||
<a href="#" class="btn btn-outline-info">
|
|
||||||
<i class="fas fa-comments me-1"></i> {% trans "All Messages" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Submitted applications --> {% endcomment %}
|
<!-- Submitted applications -->
|
||||||
<div class="kaauh-card p-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="p-6">
|
||||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
<div class="flex justify-between items-center mb-6">
|
||||||
<i class="fas fa-users me-2"></i>
|
<h5 class="text-xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="users" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
{% trans "Submitted applications" %} ({{ total_applications }})
|
{% trans "Submitted applications" %} ({{ total_applications }})
|
||||||
</h5>
|
</h5>
|
||||||
<span class="badge bg-primary-theme">{{ total_applications }}/{{ max_applications }}</span>
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-temple-red/10 text-temple-red">
|
||||||
|
{{ total_applications }}/{{ max_applications }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<div class="table-responsive">
|
<div class="overflow-x-auto">
|
||||||
<table class="table table-hover">
|
<table class="w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="border-b border-gray-200">
|
||||||
<th>{% trans "Name" %}</th>
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Name" %}</th>
|
||||||
<th>{% trans "Contact" %}</th>
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Contact" %}</th>
|
||||||
<th>{% trans "Stage" %}</th>
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Stage" %}</th>
|
||||||
<th>{% trans "Submitted" %}</th>
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Submitted" %}</th>
|
||||||
<th>{% trans "Actions" %}</th>
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for application in page_obj %}
|
{% for application in page_obj %}
|
||||||
<tr>
|
<tr class="border-b border-gray-100 hover:bg-gray-50 transition">
|
||||||
<td>
|
<td class="py-3 px-4">
|
||||||
<div class="fw-bold">{{ application.name }}</div>
|
<div class="font-semibold text-gray-900">{{ application.name }}</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="py-3 px-4">
|
||||||
<div class="small">
|
<div class="text-sm text-gray-600">
|
||||||
<div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div>
|
<div class="flex items-center gap-1">
|
||||||
<div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div>
|
<i data-lucide="mail" class="w-3 h-3"></i> {{ application.email }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<i data-lucide="phone" class="w-3 h-3"></i> {{ application.phone }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="py-3 px-4">
|
||||||
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span>
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-temple-red/10 text-temple-red">
|
||||||
|
{{ application.get_stage_display }}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="py-3 px-4">
|
||||||
<div class="small text-muted">
|
<div class="text-sm text-gray-500">
|
||||||
{{ application.created_at|date:"Y-m-d H:i" }}
|
{{ application.created_at|date:"Y-m-d H:i" }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="py-3 px-4">
|
||||||
<a href="{% url 'applicant_application_detail' application.slug %}" class="btn btn-sm btn-outline-primary" title="{% trans 'View Profile' %}">
|
<a href="{% url 'applicant_application_detail' application.slug %}" class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition" title="{% trans 'View Profile' %}">
|
||||||
<i class="fas fa-eye"></i>
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -276,127 +148,111 @@
|
|||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
{% if page_obj.has_other_pages %}
|
{% if page_obj.has_other_pages %}
|
||||||
<nav aria-label="Candidate pagination">
|
<div class="flex justify-center items-center gap-2 mt-6">
|
||||||
<ul class="pagination justify-content-center">
|
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<li class="page-item">
|
<a href="?page={{ page_obj.previous_page_number }}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-chevron-left"></i>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for num in page_obj.paginator.page_range %}
|
{% for num in page_obj.paginator.page_range %}
|
||||||
{% if page_obj.number == num %}
|
{% if page_obj.number == num %}
|
||||||
<li class="page-item active">
|
<span class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-temple-red text-white font-semibold">{{ num }}</span>
|
||||||
<span class="page-link">{{ num }}</span>
|
|
||||||
</li>
|
|
||||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||||
<li class="page-item">
|
<a href="?page={{ num }}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">{{ num }}</a>
|
||||||
<a class="page-link" href="?page={{ num }}">{{ num }}</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<li class="page-item">
|
<a href="?page={{ page_obj.next_page_number }}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
||||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}">
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-chevron-right"></i>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</div>
|
||||||
</nav>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-4">
|
<div class="text-center py-8">
|
||||||
<i class="fas fa-users fa-2x text-muted mb-3"></i>
|
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<h6 class="text-muted">{% trans "No applications submitted yet" %}</h6>
|
<i data-lucide="users" class="w-8 h-8 text-gray-400"></i>
|
||||||
<p class="text-muted small">
|
</div>
|
||||||
{% trans "Submit applications using the form above to get started." %}
|
<h6 class="text-gray-600 mb-2">{% trans "No applications submitted yet" %}</h6>
|
||||||
|
<p class="text-gray-500 text-sm">
|
||||||
|
{% trans "Submit applications using form above to get started." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Sidebar -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-lg-4 col-md-12">
|
|
||||||
<!-- Progress Card -->
|
|
||||||
<div class="col kaauh-card p-4 mb-4">
|
|
||||||
<h5 class="mb-4 text-center" style="color: var(--kaauh-teal-dark);">
|
|
||||||
{% trans "Submission Progress" %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="text-center mb-3">
|
<!-- Sidebar -->
|
||||||
<div class="progress-ring">
|
<div class="lg:col-span-1 space-y-6">
|
||||||
<svg width="120" height="120">
|
<!-- Progress Card -->
|
||||||
<circle class="progress-ring-circle"
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
stroke="#e9ecef"
|
<div class="p-6">
|
||||||
stroke-width="8"
|
<h5 class="text-xl font-bold text-gray-900 mb-4 text-center">{% trans "Submission Progress" %}</h5>
|
||||||
fill="transparent"
|
|
||||||
r="52"
|
<div class="text-center mb-4">
|
||||||
cx="60"
|
<div class="relative w-32 h-32 mx-auto">
|
||||||
cy="60"/>
|
<svg width="128" height="128" viewBox="0 0 128 128" class="transform -rotate-90">
|
||||||
<circle class="progress-ring-circle"
|
<circle cx="64" cy="64" r="56" stroke="#e5e7eb" stroke-width="8" fill="none"/>
|
||||||
stroke="var(--kaauh-teal)"
|
<circle cx="64" cy="64" r="56" stroke="#9d2235" stroke-width="8" fill="none"
|
||||||
stroke-width="8"
|
style="stroke-dasharray: 351.86; stroke-dashoffset: {{ stroke_dashoffset }};"/>
|
||||||
fill="transparent"
|
|
||||||
r="52"
|
|
||||||
cx="60"
|
|
||||||
cy="60"
|
|
||||||
style="stroke-dasharray: 326.73; stroke-dashoffset: {{ stroke_dashoffset }};"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
<div class="progress-ring-text">
|
<div class="absolute inset-0 flex items-center justify-center">
|
||||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||||
{{ progress|floatformat:0 }}%
|
<span class="text-3xl font-bold text-gray-900">{{ progress|floatformat:0 }}%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center mb-4">
|
||||||
|
<div class="text-4xl font-bold text-gray-900 mb-1">{{ total_applications }}</div>
|
||||||
|
<div class="text-gray-600">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||||
|
<div class="w-full bg-gray-200 rounded-full h-2 mb-4">
|
||||||
|
<div class="h-2 rounded-full bg-temple-red" style="width: {{ progress }}%"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="h4 mb-1">{{ total_applications }}</div>
|
|
||||||
<div class="text-muted">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="progress mt-3" style="height: 8px;">
|
|
||||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
|
||||||
<div class="progress-bar bg-primary-theme" style="width: {{ progress }}%"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-3 text-center">
|
|
||||||
{% if assignment.can_submit %}
|
{% if assignment.can_submit %}
|
||||||
<span class="badge bg-primary-theme">{% trans "Can Submit" %}</span>
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">{% trans "Can Submit" %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-danger">{% trans "Cannot Submit" %}</span>
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-red-100 text-red-800">{% trans "Cannot Submit" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Assignment Info Card -->
|
<!-- Assignment Info Card -->
|
||||||
<div class="col kaauh-card p-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
<div class="p-6">
|
||||||
<i class="fas fa-info me-2"></i>
|
<h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="info" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
{% trans "Assignment Info" %}
|
{% trans "Assignment Info" %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="space-y-4">
|
||||||
<label class="text-muted small">{% trans "Assigned Date" %}</label>
|
<div>
|
||||||
<div class="fw-bold">{{ assignment.assigned_date|date:"Y-m-d" }}</div>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Assigned Date" %}</label>
|
||||||
|
<div class="font-bold text-gray-900">{{ assignment.assigned_date|date:"Y-m-d" }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div>
|
||||||
<label class="text-muted small">{% trans "Days Remaining" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Days Remaining" %}</label>
|
||||||
<div class="fw-bold {% if assignment.days_remaining <= 3 %}text-danger{% endif %}">
|
<div class="font-bold {% if assignment.days_remaining <= 3 %}text-red-600{% endif %} text-gray-900">
|
||||||
{{ assignment.days_remaining }} {% trans "days" %}
|
{{ assignment.days_remaining }} {% trans "days" %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div>
|
||||||
<label class="text-muted small">{% trans "Submission Rate" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Submission Rate" %}</label>
|
||||||
<div class="fw-bold">
|
|
||||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||||
{{ progress|floatformat:1 }}%
|
<div class="font-bold text-gray-900">{{ progress|floatformat:1 }}%</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -404,293 +260,50 @@
|
|||||||
|
|
||||||
<!-- Recent Messages Section -->
|
<!-- Recent Messages Section -->
|
||||||
{% if message_page_obj %}
|
{% if message_page_obj %}
|
||||||
<div class="kaauh-card p-4 mt-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
<div class="p-6">
|
||||||
<i class="fas fa-comments me-2"></i>
|
<h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="message-circle" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
{% trans "Recent Messages" %}
|
{% trans "Recent Messages" %}
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<div class="row">
|
<div class="space-y-3">
|
||||||
{% for message in message_page_obj|slice:":6" %}
|
{% for message in message_page_obj|slice:":6" %}
|
||||||
<div class="col-lg-6 mb-3">
|
<div class="border-l-4 border-temple-red bg-gray-50 rounded-r-lg p-4 {% if not message.is_read %}bg-blue-50 border-blue-500{% endif %}">
|
||||||
<div class="message-item {% if not message.is_read %}unread{% endif %}">
|
<div class="flex justify-between items-start mb-2">
|
||||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
<div class="font-semibold text-gray-900">{{ message.subject }}</div>
|
||||||
<div class="fw-bold">{{ message.subject }}</div>
|
<div class="text-xs text-gray-500">{{ message.created_at|date:"Y-m-d H:i" }}</div>
|
||||||
<small class="text-muted">{{ message.created_at|date:"Y-m-d H:i" }}</small>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="small text-muted mb-2">
|
<div class="text-sm text-gray-600 mb-2">
|
||||||
{% trans "From" %}: {{ message.sender.get_full_name }}
|
{% trans "From" %}: {{ message.sender.get_full_name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="small">{{ message.message|truncatewords:30 }}</div>
|
<div class="text-sm text-gray-700">{{ message.message|truncatewords:30 }}</div>
|
||||||
{% if not message.is_read %}
|
{% if not message.is_read %}
|
||||||
<span class="badge bg-info mt-2">{% trans "New" %}</span>
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 mt-2">{% trans "New" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if message_page_obj.count > 6 %}
|
{% if message_page_obj.count > 6 %}
|
||||||
<div class="text-center mt-3">
|
<div class="text-center mt-4">
|
||||||
<a href="#" class="btn btn-outline-primary btn-sm">
|
<a href="#" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl transition">
|
||||||
{% trans "View All Messages" %}
|
{% trans "View All Messages" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Message Modal -->
|
|
||||||
<div class="modal fade" id="messageModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
|
||||||
<i class="fas fa-envelope me-2"></i>
|
|
||||||
{% trans "Send Message to Admin" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="#">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="assignment_id" value="{{ assignment.id }}">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="subject" class="form-label">
|
|
||||||
{% trans "Subject" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="subject" name="subject" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="priority" class="form-label">{% trans "Priority" %}</label>
|
|
||||||
<select class="form-select" id="priority" name="priority">
|
|
||||||
<option value="low">{% trans "Low" %}</option>
|
|
||||||
<option value="medium" selected>{% trans "Medium" %}</option>
|
|
||||||
<option value="high">{% trans "High" %}</option>
|
|
||||||
<option value="urgent">{% trans "Urgent" %}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="content" class="form-label">
|
|
||||||
{% trans "Message" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="content" name="content" rows="5" required></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">
|
|
||||||
{% trans "Cancel" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-paper-plane me-1"></i> {% trans "Send Message" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Edit application Modal -->
|
|
||||||
<div class="modal fade" id="editCandidateModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
|
||||||
<i class="fas fa-edit me-2"></i>
|
|
||||||
{% trans "Edit application" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<form id="editCandidateForm" method="post" action="{% url 'agency_portal_edit_application' 0 %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" id="edit_candidate_id" name="candidate_id">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_first_name" class="form-label">
|
|
||||||
{% trans "First Name" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="edit_first_name" name="first_name" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_last_name" class="form-label">
|
|
||||||
{% trans "Last Name" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="edit_last_name" name="last_name" required>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_email" class="form-label">
|
|
||||||
{% trans "Email" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="email" class="form-control" id="edit_email" name="email" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_phone" class="form-label">
|
|
||||||
{% trans "Phone" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="tel" class="form-control" id="edit_phone" name="phone" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="edit_address" class="form-label">
|
|
||||||
{% trans "Address" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="edit_address" name="address" rows="3" required></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
||||||
{% trans "Cancel" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Save Changes" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Delete Confirmation Modal -->
|
|
||||||
<div class="modal fade" id="deleteCandidateModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
|
||||||
<i class="fas fa-trash me-2"></i>
|
|
||||||
{% trans "Remove application" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<form id="deleteCandidateForm" method="post" action="{% url 'agency_portal_delete_application' 0 %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" id="delete_candidate_id" name="candidate_id">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
||||||
{% trans "Are you sure you want to remove this application? This action cannot be undone." %}
|
|
||||||
</div>
|
|
||||||
<p><strong>{% trans "Application:" %}</strong> <span id="delete_candidate_name"></span></p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
||||||
{% trans "Cancel" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-danger">
|
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Remove Application" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
<script>
|
||||||
// Edit Candidate
|
|
||||||
function editCandidate(candidateId) {
|
|
||||||
// Update form action URL with candidate ID
|
|
||||||
const editForm = document.getElementById('editCandidateForm');
|
|
||||||
editForm.action = editForm.action.replace('/0/', `/${candidateId}/`);
|
|
||||||
|
|
||||||
// Fetch candidate data and populate modal
|
|
||||||
fetch(`/api/candidate/${candidateId}/`)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
document.getElementById('edit_candidate_id').value = data.id;
|
|
||||||
document.getElementById('edit_first_name').value = data.first_name;
|
|
||||||
document.getElementById('edit_last_name').value = data.last_name;
|
|
||||||
document.getElementById('edit_email').value = data.email;
|
|
||||||
document.getElementById('edit_phone').value = data.phone;
|
|
||||||
document.getElementById('edit_address').value = data.address;
|
|
||||||
|
|
||||||
new bootstrap.Modal(document.getElementById('editCandidateModal')).show();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error fetching Application:', error);
|
|
||||||
alert('{% trans "Error loading Application data. Please try again." %}');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete Application
|
|
||||||
function deleteCandidate(candidateId, candidateName) {
|
|
||||||
// Update form action URL with candidate ID
|
|
||||||
const deleteForm = document.getElementById('deleteCandidateForm');
|
|
||||||
deleteForm.action = deleteForm.action.replace('/0/', `/${candidateId}/`);
|
|
||||||
|
|
||||||
document.getElementById('delete_candidate_id').value = candidateId;
|
|
||||||
document.getElementById('delete_candidate_name').textContent = candidateName;
|
|
||||||
|
|
||||||
new bootstrap.Modal(document.getElementById('deleteCandidateModal')).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle form submissions
|
|
||||||
document.getElementById('editCandidateForm').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const formData = new FormData(this);
|
|
||||||
|
|
||||||
fetch(this.action, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
bootstrap.Modal.getInstance(document.getElementById('editCandidateModal')).hide();
|
|
||||||
location.reload();
|
|
||||||
} else {
|
|
||||||
alert(data.message || '{% trans "Error updating Application. Please try again." %}');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('{% trans "Error updating Application. Please try again." %}');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('deleteCandidateForm').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const formData = new FormData(this);
|
|
||||||
|
|
||||||
fetch(this.action, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
bootstrap.Modal.getInstance(document.getElementById('deleteCandidateModal')).hide();
|
|
||||||
location.reload();
|
|
||||||
} else {
|
|
||||||
alert(data.message || '{% trans "Error removing Application. Please try again." %}');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
alert('{% trans "Error removing Application. Please try again." %}');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto-focus on first input in submission form
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const firstNameField = document.getElementById('first_name');
|
lucide.createIcons();
|
||||||
if (firstNameField) {
|
|
||||||
firstNameField.focus();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,219 +1,204 @@
|
|||||||
{% extends 'portal_base.html' %}
|
{% extends 'portal_base.html' %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "Agency Dashboard" %} -{% trans "Aagency Portal" %}{% endblock %}
|
{% block title %}{% trans "Agency Dashboard" %} - {% trans "Agency Portal" %}{% endblock %}
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-success: #28a745;
|
|
||||||
--kaauh-info: #17a2b8;
|
|
||||||
--kaauh-danger: #dc3545;
|
|
||||||
--kaauh-warning: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kaauh-card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock%}
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="space-y-6">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<div class="px-2 py-2">
|
<!-- Header Section -->
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white rounded-2xl shadow-lg p-6 md:p-8">
|
||||||
<i class="fas fa-tachometer-alt me-2"></i>
|
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||||
{% trans "Agency Dashboard" %}
|
<div>
|
||||||
</h1>
|
<div class="flex items-center gap-3 mb-2">
|
||||||
<p class="text-muted mb-0">
|
<div class="w-14 h-14 rounded-xl bg-white/20 backdrop-blur-sm flex items-center justify-center">
|
||||||
|
<i data-lucide="building" class="w-8 h-8"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-2xl md:text-3xl font-bold">{% trans "Agency Dashboard" %}</h1>
|
||||||
|
<p class="text-white/80">
|
||||||
{% trans "Welcome back" %}, {{ agency.name }}!
|
{% trans "Welcome back" %}, {{ agency.name }}!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
{% comment %} <a href="{% url 'agency_portal_submit_application' %}" class="btn btn-main-action me-2">
|
</div>
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
>
|
|
||||||
{% endif %}
|
|
||||||
</a> {% endcomment %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Overview Statistics -->
|
<!-- Statistics Cards -->
|
||||||
<div class="row mb-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<div class="col-md-3 mb-2">
|
<!-- Total Assignments -->
|
||||||
<div class="kaauh-card shadow-sm h-100">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="card-body text-center px-2 py-2">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<div class="text-primary-theme mb-2">
|
<div class="flex items-center gap-3">
|
||||||
<i class="fas fa-briefcase fa-2x"></i>
|
<i data-lucide="briefcase" class="w-6 h-6"></i>
|
||||||
</div>
|
<span class="text-sm font-semibold">{% trans "Total Assignments" %}</span>
|
||||||
<h4 class="card-title">{{ total_assignments }}</h4>
|
|
||||||
<p class="card-text text-muted">{% trans "Total Assignments" %}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="p-5 text-center">
|
||||||
<div class="col-md-3 mb-2">
|
<div class="text-3xl font-bold text-temple-red mb-1">{{ total_assignments }}</div>
|
||||||
<div class="kaauh-card shadow-sm h-100 px-2 py-2">
|
<p class="text-xs text-gray-500 uppercase tracking-wide">{% trans "Assigned Jobs" %}</p>
|
||||||
<div class="card-body text-center">
|
|
||||||
<div class="text-primary-theme mb-2">
|
|
||||||
<i class="fas fa-check-circle fa-2x"></i>
|
|
||||||
</div>
|
|
||||||
<h4 class="card-title">{{ active_assignments }}</h4>
|
|
||||||
<p class="card-text text-muted">{% trans "Active Assignments" %}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="col-md-3 mb-2">
|
<!-- Active Assignments -->
|
||||||
<div class="kaauh-card shadow-sm h-100 px-2 py-2">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="card-body text-center">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<div class="text-primary-theme mb-2">
|
<div class="flex items-center gap-3">
|
||||||
<i class="fas fa-users fa-2x"></i>
|
<i data-lucide="check-circle" class="w-6 h-6"></i>
|
||||||
</div>
|
<span class="text-sm font-semibold">{% trans "Active Assignments" %}</span>
|
||||||
<h4 class="card-title">{{ total_applications }}</h4>
|
|
||||||
<p class="card-text text-muted">{% trans "Total Applications" %}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-5 text-center">
|
||||||
|
<div class="text-3xl font-bold text-temple-red mb-1">{{ active_assignments }}</div>
|
||||||
|
<p class="text-xs text-gray-500 uppercase tracking-wide">{% trans "In Progress" %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="kaauh-card shadow-sm h-100 px-2 py-2">
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<div class="text-warning mb-2">
|
|
||||||
<i class="fas fa-envelope fa-2x"></i>
|
|
||||||
</div>
|
</div>
|
||||||
<h4 class="card-title">{{ total_unread_messages }}</h4>
|
|
||||||
<p class="card-text text-muted">{% trans "Unread Messages" %}</p>
|
<!-- Total Applications -->
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<i data-lucide="users" class="w-6 h-6"></i>
|
||||||
|
<span class="text-sm font-semibold">{% trans "Total Applications" %}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-5 text-center">
|
||||||
|
<div class="text-3xl font-bold text-temple-red mb-1">{{ total_applications }}</div>
|
||||||
|
<p class="text-xs text-gray-500 uppercase tracking-wide">{% trans "Candidates Submitted" %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Unread Messages -->
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<i data-lucide="mail" class="w-6 h-6"></i>
|
||||||
|
<span class="text-sm font-semibold">{% trans "Unread Messages" %}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-5 text-center">
|
||||||
|
<div class="text-3xl font-bold text-yellow-600 mb-1">{{ total_unread_messages }}</div>
|
||||||
|
<p class="text-xs text-gray-500 uppercase tracking-wide">{% trans "New Communications" %}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Job Assignments List -->
|
<!-- Job Assignments List -->
|
||||||
<div class="kaauh-card shadow-sm px-3 py-3">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="card-body">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-5">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="flex justify-between items-center">
|
||||||
<h5 class="card-title mb-0">
|
<div class="flex items-center gap-3">
|
||||||
<i class="fas fa-tasks me-2"></i>
|
<i data-lucide="list-todo" class="w-6 h-6"></i>
|
||||||
{% trans "Your Job Assignments" %}
|
<h2 class="text-lg font-bold">{% trans "Your Job Assignments" %}</h2>
|
||||||
</h5>
|
</div>
|
||||||
<span class="badge bg-secondary">{{ assignment_stats|length }} {% trans "assignments" %}</span>
|
<span class="inline-flex items-center px-3 py-1.5 rounded-lg bg-white/20 backdrop-blur-sm text-sm font-semibold">
|
||||||
|
{{ assignment_stats|length }} {% trans "assignments" %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="p-6">
|
||||||
{% if assignment_stats %}
|
{% if assignment_stats %}
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4">
|
||||||
{% for stats in assignment_stats %}
|
{% for stats in assignment_stats %}
|
||||||
<div class="col-lg-6 col-xl-4 mb-4">
|
<div class="border border-gray-200 rounded-xl overflow-hidden hover:border-temple-red transition-all duration-200 hover:shadow-lg">
|
||||||
<div class="card h-100 border-0 shadow-sm assignment-card">
|
|
||||||
<div class="card-body">
|
|
||||||
<!-- Assignment Header -->
|
<!-- Assignment Header -->
|
||||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
<div class="bg-temple-cream border-b border-gray-200 p-4">
|
||||||
<div class="flex-grow-1">
|
<div class="flex justify-between items-start gap-2 mb-2">
|
||||||
<h6 class="card-title mb-1">
|
<div class="flex-1">
|
||||||
|
<h5 class="font-bold text-temple-dark mb-1">
|
||||||
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
|
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
|
||||||
class="text-decoration-none text-dark">
|
class="text-temple-dark hover:text-temple-red transition">
|
||||||
{{ stats.assignment.job.title }}
|
{{ stats.assignment.job.title }}
|
||||||
</a>
|
</a>
|
||||||
</h6>
|
</h5>
|
||||||
<p class="text-muted small mb-2">
|
<p class="text-sm text-gray-600">
|
||||||
<i class="fas fa-building me-1"></i>
|
<i data-lucide="building-2" class="w-3 h-3 inline mr-1"></i>
|
||||||
{{ stats.assignment.job.department }}
|
{{ stats.assignment.job.department|default:"N/A" }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-end">
|
<div class="shrink-0">
|
||||||
{% if stats.is_active %}
|
{% if stats.is_active %}
|
||||||
<span class="badge bg-success">{% trans "Active" %}</span>
|
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide bg-green-600 text-white">
|
||||||
|
{% trans "Active" %}
|
||||||
|
</span>
|
||||||
{% elif stats.assignment.status == 'COMPLETED' %}
|
{% elif stats.assignment.status == 'COMPLETED' %}
|
||||||
<span class="badge bg-primary">{% trans "Completed" %}</span>
|
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide bg-gray-600 text-white">
|
||||||
|
{% trans "Completed" %}
|
||||||
|
</span>
|
||||||
{% elif stats.assignment.status == 'CANCELLED' %}
|
{% elif stats.assignment.status == 'CANCELLED' %}
|
||||||
<span class="badge bg-danger">{% trans "Cancelled" %}</span>
|
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide bg-red-600 text-white">
|
||||||
|
{% trans "Cancelled" %}
|
||||||
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-warning">{% trans "Expired" %}</span>
|
<span class="inline-flex items-center px-2.5 py-1 rounded-full text-[10px] font-bold uppercase tracking-wide bg-yellow-600 text-white">
|
||||||
|
{% trans "Expired" %}
|
||||||
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Assignment Details -->
|
<!-- Assignment Body -->
|
||||||
<div class="row mb-3">
|
<div class="p-4 space-y-3">
|
||||||
<div class="col-6">
|
<!-- Deadline & Applications -->
|
||||||
<small class="text-muted d-block">{% trans "Deadline" %}</small>
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<strong class="{% if stats.days_remaining <= 3 %}text-danger{% elif stats.days_remaining <= 7 %}text-warning{% else %}text-success{% endif %}">
|
<div>
|
||||||
|
<div class="text-xs text-gray-500 mb-1">{% trans "Deadline" %}</div>
|
||||||
|
<div class="font-semibold {% if stats.days_remaining <= 3 %}text-red-600{% elif stats.days_remaining <= 7 %}text-yellow-600{% else %}text-green-600{% endif %}">
|
||||||
{{ stats.assignment.deadline|date:"Y-m-d" }}
|
{{ stats.assignment.deadline|date:"Y-m-d" }}
|
||||||
|
<div class="text-xs text-gray-500 font-normal">
|
||||||
{% if stats.days_remaining >= 0 %}
|
{% if stats.days_remaining >= 0 %}
|
||||||
({{ stats.days_remaining }} {% trans "days left" %})
|
({{ stats.days_remaining }} {% trans "days left" %})
|
||||||
{% else %}
|
{% else %}
|
||||||
({{ stats.days_remaining }} {% trans "days overdue" %})
|
({{ stats.days_remaining }} {% trans "days overdue" %})
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</strong>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
</div>
|
||||||
<small class="text-muted d-block">{% trans "Applications" %}</small>
|
</div>
|
||||||
<strong>{{ stats.application_count }} / {{ stats.assignment.max_candidates}}</strong>
|
<div>
|
||||||
|
<div class="text-xs text-gray-500 mb-1">{% trans "Applications" %}</div>
|
||||||
|
<div class="font-semibold">
|
||||||
|
{{ stats.application_count }} / {{ stats.assignment.max_candidates }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Progress Bar -->
|
<!-- Progress Bar -->
|
||||||
<div class="mb-3">
|
<div>
|
||||||
<div class="d-flex justify-content-between mb-1">
|
<div class="flex justify-between text-xs text-gray-500 mb-1">
|
||||||
<small class="text-muted">{% trans "Submission Progress" %}</small>
|
<span>{% trans "Submission Progress" %}</span>
|
||||||
<small class="text-muted">{{ stats.application_count }}/{{ stats.assignment.max_candidates }}</small>
|
<span>{{ stats.application_count }}/{{ stats.assignment.max_candidates }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="progress" style="height: 6px;">
|
<div class="w-full bg-gray-200 rounded-full h-2 overflow-hidden">
|
||||||
{% with progress=stats.application_count %}
|
{% with progress=stats.application_count %}
|
||||||
<div class="progress-bar {% if progress >= 90 %}bg-danger{% elif progress >= 70 %}bg-warning{% else %}bg-success{% endif %}"
|
{% widthratio progress stats.assignment.max_candidates 100 as percentage %}
|
||||||
style="width: {{ progress|floatformat:0 }}%"></div>
|
<div class="h-full rounded-full transition-all duration-300 {% if percentage >= 90 %}bg-red-600{% elif percentage >= 70 %}bg-yellow-500{% else %}bg-temple-red{% endif %}"
|
||||||
|
style="width: {{ percentage }}%"></div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action Buttons -->
|
<!-- Action Buttons -->
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="flex gap-2 pt-2 border-t border-gray-100">
|
||||||
<div>
|
|
||||||
{% if stats.can_submit %}
|
{% if stats.can_submit %}
|
||||||
<a href="{% url 'agency_portal_submit_application_page' stats.assignment.slug %}"
|
<a href="{% url 'agency_portal_submit_application_page' stats.assignment.slug %}"
|
||||||
class="btn btn-sm btn-main-action">
|
class="flex-1 inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2 rounded-lg text-sm transition">
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %}
|
<i data-lucide="user-plus" class="w-4 h-4"></i>
|
||||||
|
{% trans "Submit" %}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<button class="btn btn-sm btn-secondary" disabled>
|
<button class="flex-1 inline-flex items-center justify-center gap-2 bg-gray-200 text-gray-600 font-medium px-4 py-2 rounded-lg text-sm cursor-not-allowed" disabled>
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submissions Closed" %}
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Closed" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
|
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
|
||||||
class="btn btn-sm btn-main-action">
|
class="inline-flex items-center justify-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white font-medium px-4 py-2 rounded-lg text-sm transition">
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
|
{% trans "View" %}
|
||||||
</a>
|
</a>
|
||||||
{% comment %} {% if stats.unread_messages > 0 %}
|
|
||||||
<a href="{% url 'message_list' %}"
|
|
||||||
class="btn btn-sm btn-outline-warning position-relative">
|
|
||||||
<i class="fas fa-envelope me-1"></i>
|
|
||||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
|
||||||
{{ stats.unread_messages }}
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
{% endif %} {% endcomment %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -221,10 +206,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{% include "includes/paginator.html" %}
|
{% include "includes/paginator.html" %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-5">
|
<div class="text-center py-12">
|
||||||
<i class="fas fa-briefcase fa-3x text-muted mb-3"></i>
|
<i data-lucide="briefcase" class="w-20 h-20 text-gray-300 mx-auto mb-4"></i>
|
||||||
<h5 class="text-muted">{% trans "No Job Assignments Found" %}</h5>
|
<h5 class="text-xl font-bold text-gray-900 mb-2">{% trans "No Job Assignments Found" %}</h5>
|
||||||
<p class="text-muted">
|
<p class="text-gray-500 max-w-md mx-auto">
|
||||||
{% trans "You don't have any job assignments yet. Please contact the administrator if you expect to have assignments." %}
|
{% trans "You don't have any job assignments yet. Please contact the administrator if you expect to have assignments." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -234,24 +219,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.assignment-card {
|
.card-hover {
|
||||||
transition: transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.assignment-card:hover {
|
.card-hover:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.assignment-card .card-title a:hover {
|
.progress-bar-animated {
|
||||||
color: var(--kaauh-teal-dark) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
background-color: #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
transition: width 0.3s ease;
|
transition: width 0.3s ease;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -259,11 +236,13 @@
|
|||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Auto-refresh for unread messages count
|
// Auto-refresh for unread messages count
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
// You could implement a lightweight API call here to check for new messages
|
// You could implement a lightweight API call here to check for new messages
|
||||||
// For now, just refresh the page every 5 minutes
|
// For now, just refresh page every 5 minutes
|
||||||
location.reload();
|
location.reload();
|
||||||
}, 300000); // 5 minutes
|
}, 300000); // 5 minutes
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,294 +1,191 @@
|
|||||||
{% extends 'portal_base.html' %}
|
{% extends 'portal_base.html' %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "Agency Portal Login" %} - ATS{% endblock %}
|
{% block title %}{% trans "Agency Portal Login" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* KAAT-S UI Variables */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-success: #28a745;
|
|
||||||
--kaauh-info: #17a2b8;
|
|
||||||
--kaauh-danger: #dc3545;
|
|
||||||
--kaauh-warning: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-card {
|
|
||||||
background: white;
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
|
|
||||||
border: none;
|
|
||||||
max-width: 650px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-header {
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 1rem 1rem 0 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-body {
|
|
||||||
padding: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus {
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-login {
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.75rem 2rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-login:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 25px rgba(0, 99, 110, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group-text {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-icon {
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
color: white;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-section {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-danger {
|
|
||||||
background-color: #f8d7da;
|
|
||||||
color: #721c24;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-info {
|
|
||||||
background-color: #d1ecf1;
|
|
||||||
color: #0c5460;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="login-container">
|
<body class="bg-gradient-to-br from-temple-red to-[#7a1a29] min-h-screen">
|
||||||
<div class="login-card">
|
<div class="min-h-screen flex items-center justify-center p-6">
|
||||||
|
<div class="bg-white rounded-2xl shadow-2xl border-none max-w-2xl w-full">
|
||||||
|
|
||||||
<!-- Login Header -->
|
<!-- Login Header -->
|
||||||
<div class="login-header">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-8 rounded-t-2xl text-center">
|
||||||
<div class="mb-3">
|
<div class="mb-4 inline-flex items-center justify-center w-20 h-20 rounded-full bg-white/20 backdrop-blur-sm">
|
||||||
<i class="fas fa-building fa-3x"></i>
|
<i data-lucide="building" class="w-10 h-10"></i>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="mb-2">{% trans "Agency Portal" %}</h3>
|
<h2 class="text-2xl md:text-3xl font-bold mb-2">{% trans "Agency Portal" %}</h2>
|
||||||
<p class="mb-0 opacity-75">
|
<p class="text-white/80 text-sm md:text-base">
|
||||||
{% trans "Submit candidates for job assignments" %}
|
{% trans "Submit candidates for job assignments" %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Login Body -->
|
<!-- Login Body -->
|
||||||
<div class="login-body">
|
<div class="p-8 md:p-10">
|
||||||
<!-- Messages -->
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Login Form -->
|
<!-- Login Form -->
|
||||||
<form method="post" novalidate>
|
<form method="post" novalidate id="login-form" class="space-y-6">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<!-- Access Token Field -->
|
<!-- Access Token Field -->
|
||||||
<div class="mb-3">
|
<div class="space-y-2">
|
||||||
<label for="{{ form.token.id_for_label }}" class="form-label fw-bold">
|
<label for="{{ form.token.id_for_label }}" class="block text-sm font-bold text-gray-700">
|
||||||
<i class="fas fa-key me-2"></i>
|
<i data-lucide="key" class="w-4 h-4 inline mr-2"></i>
|
||||||
{% trans "Access Token" %}
|
{% trans "Access Token" %}
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="relative">
|
||||||
<span class="input-group-text">
|
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-temple-red">
|
||||||
<i class="fas fa-lock"></i>
|
<i data-lucide="lock" class="w-5 h-5"></i>
|
||||||
</span>
|
</span>
|
||||||
{{ form.token }}
|
<input type="text"
|
||||||
|
name="token"
|
||||||
|
id="{{ form.token.id_for_label }}"
|
||||||
|
class="w-full pl-12 pr-4 py-3.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
|
||||||
|
placeholder="{% trans 'Enter your access token' %}"
|
||||||
|
required>
|
||||||
</div>
|
</div>
|
||||||
{% if form.token.errors %}
|
{% if form.token.errors %}
|
||||||
<div class="text-danger small mt-1">
|
<div class="text-red-600 text-sm mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-4 h-4"></i>
|
||||||
{% for error in form.token.errors %}{{ error }}{% endfor %}
|
{% for error in form.token.errors %}{{ error }}{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<small class="form-text text-muted">
|
<p class="text-xs text-gray-500">
|
||||||
{% trans "Enter the access token provided by the hiring organization" %}
|
{% trans "Enter the access token provided by the hiring organization" %}
|
||||||
</small>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Password Field -->
|
<!-- Password Field -->
|
||||||
<div class="mb-4">
|
<div class="space-y-2">
|
||||||
<label for="{{ form.password.id_for_label }}" class="form-label fw-bold">
|
<label for="{{ form.password.id_for_label }}" class="block text-sm font-bold text-gray-700">
|
||||||
<i class="fas fa-shield-alt me-2"></i>
|
<i data-lucide="shield-check" class="w-4 h-4 inline mr-2"></i>
|
||||||
{% trans "Password" %}
|
{% trans "Password" %}
|
||||||
</label>
|
</label>
|
||||||
<div class="input-group">
|
<div class="relative">
|
||||||
<span class="input-group-text">
|
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-temple-red">
|
||||||
<i class="fas fa-key"></i>
|
<i data-lucide="key" class="w-5 h-5"></i>
|
||||||
</span>
|
</span>
|
||||||
{{ form.password }}
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
id="{{ form.password.id_for_label }}"
|
||||||
|
class="w-full pl-12 pr-12 py-3.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
|
||||||
|
placeholder="{% trans 'Enter your password' %}"
|
||||||
|
required>
|
||||||
|
<button type="button"
|
||||||
|
onclick="togglePassword()"
|
||||||
|
id="password-toggle"
|
||||||
|
class="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition">
|
||||||
|
<i data-lucide="eye" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% if form.password.errors %}
|
{% if form.password.errors %}
|
||||||
<div class="text-danger small mt-1">
|
<div class="text-red-600 text-sm mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-4 h-4"></i>
|
||||||
{% for error in form.password.errors %}{{ error }}{% endfor %}
|
{% for error in form.password.errors %}{{ error }}{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<small class="form-text text-muted">
|
<p class="text-xs text-gray-500">
|
||||||
{% trans "Enter the password for this access token" %}
|
{% trans "Enter the password for this access token" %}
|
||||||
</small>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Submit Button -->
|
<!-- Submit Button -->
|
||||||
<div class="d-grid">
|
<button type="submit"
|
||||||
<button type="submit" class="btn btn-login btn-lg">
|
class="w-full bg-gradient-to-r from-temple-red to-[#7a1a29] hover:from-[#8a1d2d] hover:to-[#6a1520] text-white font-bold py-4 rounded-xl text-sm transition shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 flex items-center justify-center gap-2">
|
||||||
<i class="fas fa-sign-in-alt me-2"></i>
|
<i data-lucide="log-in" class="w-5 h-5"></i>
|
||||||
{% trans "Access Portal" %}
|
{% trans "Access Portal" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Information Section -->
|
<!-- Information Section -->
|
||||||
<div class="info-section">
|
<div class="mt-8 bg-temple-cream rounded-xl p-6 border border-gray-200">
|
||||||
<h6 class="fw-bold mb-3" style="color: var(--kaauh-teal-dark);">
|
<h5 class="text-temple-dark font-bold mb-4 flex items-center gap-2">
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
<i data-lucide="help-circle" class="w-5 h-5 text-temple-red"></i>
|
||||||
{% trans "Need Help?" %}
|
{% trans "Need Help?" %}
|
||||||
</h6>
|
</h5>
|
||||||
|
|
||||||
<div class="row text-center">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 text-center">
|
||||||
<div class="col-6 mb-3">
|
<div>
|
||||||
<div class="feature-icon mx-auto">
|
<div class="w-16 h-16 mx-auto mb-3 rounded-full bg-gradient-to-br from-temple-red to-[#7a1a29] flex items-center justify-center text-white">
|
||||||
<i class="fas fa-envelope"></i>
|
<i data-lucide="mail" class="w-8 h-8"></i>
|
||||||
</div>
|
</div>
|
||||||
<h6 class="fw-bold">{% trans "Contact Support" %}</h6>
|
<h6 class="font-bold text-gray-900 mb-1">{% trans "Contact Support" %}</h6>
|
||||||
<small class="text-muted">
|
<p class="text-sm text-gray-500">
|
||||||
{% trans "Reach out to your hiring contact" %}
|
{% trans "Reach out to your hiring contact" %}
|
||||||
</small>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 mb-3">
|
<div>
|
||||||
<div class="feature-icon mx-auto">
|
<div class="w-16 h-16 mx-auto mb-3 rounded-full bg-gradient-to-br from-temple-red to-[#7a1a29] flex items-center justify-center text-white">
|
||||||
<i class="fas fa-question-circle"></i>
|
<i data-lucide="book-open" class="w-8 h-8"></i>
|
||||||
</div>
|
</div>
|
||||||
<h6 class="fw-bold">{% trans "Documentation" %}</h6>
|
<h6 class="font-bold text-gray-900 mb-1">{% trans "Documentation" %}</h6>
|
||||||
<small class="text-muted">
|
<p class="text-sm text-gray-500">
|
||||||
{% trans "View user guides and tutorials" %}
|
{% trans "View user guides and tutorials" %}
|
||||||
</small>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Security Notice -->
|
<!-- Security Notice -->
|
||||||
<div class="alert alert-info mt-3 mb-0">
|
<div class="mt-6 bg-blue-50 border border-blue-200 rounded-xl p-5">
|
||||||
<h6 class="alert-heading">
|
<h6 class="text-blue-800 font-bold mb-3 flex items-center gap-2">
|
||||||
<i class="fas fa-shield-alt me-2"></i>
|
<i data-lucide="shield-check" class="w-4 h-4"></i>
|
||||||
{% trans "Security Notice" %}
|
{% trans "Security Notice" %}
|
||||||
</h6>
|
</h6>
|
||||||
<p class="mb-2 small">
|
<p class="text-blue-700 text-sm mb-3">
|
||||||
{% trans "This portal is for authorized agency partners only. Access is monitored and logged." %}
|
{% trans "This portal is for authorized agency partners only. Access is monitored and logged." %}
|
||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr class="border-blue-200 mb-3">
|
||||||
<p class="mb-0 small">
|
<p class="text-blue-700 text-sm mb-0">
|
||||||
{% trans "If you believe you've received this link in error, please contact the hiring organization immediately." %}
|
{% trans "If you believe you've received this link in error, please contact the hiring organization immediately." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast Notification Container -->
|
||||||
|
<div id="toast-container"></div>
|
||||||
|
</body>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
lucide.createIcons();
|
||||||
// Focus on access token field
|
|
||||||
const accessTokenField = document.getElementById('{{ form.token.id_for_label }}');
|
|
||||||
if (accessTokenField) {
|
|
||||||
accessTokenField.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-format access token (remove spaces and convert to uppercase)
|
// Auto-format access token (remove spaces and convert to uppercase)
|
||||||
const accessTokenInput = document.getElementById('{{ form.token.id_for_label }}');
|
const accessTokenInput = document.getElementById('{{ form.token.id_for_label }}');
|
||||||
if (accessTokenInput) {
|
if (accessTokenInput) {
|
||||||
accessTokenInput.addEventListener('input', function(e) {
|
accessTokenInput.addEventListener('input', function() {
|
||||||
// Remove spaces and convert to uppercase
|
// Remove spaces and convert to uppercase
|
||||||
this.value = this.value.replace(/\s+/g, '');
|
this.value = this.value.replace(/\s+/g, '').toUpperCase();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Focus on load
|
||||||
|
accessTokenInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show/hide password functionality
|
// Toggle password visibility
|
||||||
|
function togglePassword() {
|
||||||
const passwordField = document.getElementById('{{ form.password.id_for_label }}');
|
const passwordField = document.getElementById('{{ form.password.id_for_label }}');
|
||||||
const passwordToggle = document.createElement('button');
|
const toggleBtn = document.getElementById('password-toggle');
|
||||||
passwordToggle.type = 'button';
|
|
||||||
passwordToggle.className = 'btn btn-outline-secondary';
|
|
||||||
passwordToggle.innerHTML = '<i class="fas fa-eye"></i>';
|
|
||||||
passwordToggle.style.position = 'absolute';
|
|
||||||
passwordToggle.style.right = '10px';
|
|
||||||
passwordToggle.style.top = '50%';
|
|
||||||
passwordToggle.style.transform = 'translateY(-50%)';
|
|
||||||
passwordToggle.style.border = 'none';
|
|
||||||
passwordToggle.style.background = 'none';
|
|
||||||
passwordToggle.style.zIndex = '10';
|
|
||||||
|
|
||||||
if (passwordField && passwordField.parentElement) {
|
if (passwordField && toggleBtn) {
|
||||||
passwordField.parentElement.style.position = 'relative';
|
|
||||||
|
|
||||||
passwordToggle.addEventListener('click', function() {
|
|
||||||
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
|
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||||
passwordField.setAttribute('type', type);
|
passwordField.setAttribute('type', type);
|
||||||
this.innerHTML = type === 'password' ? '<i class="fas fa-eye"></i>' : '<i class="fas fa-eye-slash"></i>';
|
|
||||||
});
|
|
||||||
|
|
||||||
passwordField.parentElement.appendChild(passwordToggle);
|
const icon = type === 'password' ? 'eye' : 'eye-off';
|
||||||
|
toggleBtn.innerHTML = `<i data-lucide="${icon}" class="w-5 h-5"></i>`;
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Form validation
|
// Form validation
|
||||||
const form = document.querySelector('form');
|
const form = document.getElementById('login-form');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener('submit', function(e) {
|
||||||
const accessToken = accessTokenInput.value.trim();
|
const accessToken = accessTokenInput.value.trim();
|
||||||
const password = passwordField.value.trim();
|
const passwordField = document.getElementById('{{ form.password.id_for_label }}');
|
||||||
|
const password = passwordField ? passwordField.value.trim() : '';
|
||||||
|
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -307,29 +204,42 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function showError(message) {
|
function showError(message) {
|
||||||
// Remove existing alerts
|
// Remove existing toasts
|
||||||
const existingAlerts = document.querySelectorAll('.alert-danger');
|
const container = document.getElementById('toast-container');
|
||||||
existingAlerts.forEach(alert => alert.remove());
|
container.innerHTML = '';
|
||||||
|
|
||||||
// Create new alert
|
// Create new toast
|
||||||
const alertDiv = document.createElement('div');
|
const toast = document.createElement('div');
|
||||||
alertDiv.className = 'alert alert-danger alert-dismissible fade show';
|
toast.className = 'fixed top-4 right-4 bg-red-600 text-white px-6 py-4 rounded-xl shadow-lg z-50 flex items-center gap-3 animate-pulse';
|
||||||
alertDiv.innerHTML = `
|
toast.innerHTML = `
|
||||||
${message}
|
<i data-lucide="alert-circle" class="w-5 h-5"></i>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
<span class="flex-1">${message}</span>
|
||||||
|
<button onclick="this.parentElement.remove()" class="hover:bg-red-700 p-1 rounded-lg transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Insert at the top of the login body
|
container.appendChild(toast);
|
||||||
const loginBody = document.querySelector('.login-body');
|
lucide.createIcons();
|
||||||
loginBody.insertBefore(alertDiv, loginBody.firstChild);
|
|
||||||
|
|
||||||
// Auto-dismiss after 5 seconds
|
// Auto-dismiss after 5 seconds
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (alertDiv.parentNode) {
|
if (toast.parentNode) {
|
||||||
alertDiv.remove();
|
toast.classList.add('animate-fade-out');
|
||||||
|
setTimeout(() => toast.remove(), 300);
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
@keyframes fade-out {
|
||||||
|
from { opacity: 1; transform: translateY(0); }
|
||||||
|
to { opacity: 0; transform: translateY(-20px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-out {
|
||||||
|
animation: fade-out 0.3s ease-out forwards;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -2,232 +2,126 @@
|
|||||||
{% load static i18n crispy_forms_tags %}
|
{% load static i18n crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}{% trans "Agency Applicant List" %} - ATS{% endblock %}
|
{% block title %}{% trans "Agency Applicant List" %} - ATS{% endblock %}
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-success: #28a745;
|
|
||||||
--kaauh-info: #17a2b8;
|
|
||||||
--kaauh-danger: #dc3545;
|
|
||||||
--kaauh-warning: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kaauh-card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.person-row:hover {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stage-badge {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-form {
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock%}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4 persons-list">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<!-- Header -->
|
||||||
<div class="px-2 py-2">
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6 px-2 py-2">
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<div>
|
||||||
<i class="fas fa-users me-2"></i>
|
<h1 class="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
|
||||||
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="users" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
{% trans "All Applicants" %}
|
{% trans "All Applicants" %}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">
|
<p class="text-gray-600">{% trans "All applicants who come through" %} {{ agency.name }}</p>
|
||||||
{% trans "All applicants who come through" %} {{ agency.name }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl transition shadow-sm hover:shadow-md" data-bs-toggle="modal" data-bs-target="#personModal">
|
||||||
<!-- Add Person Button -->
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add New Applicant" %}
|
||||||
<button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#personModal">
|
|
||||||
<i class="fas fa-plus me-1"></i> {% trans "Add New Applicant" %}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search and Filter Section -->
|
<!-- Search and Filter Section -->
|
||||||
<div class="kaauh-card shadow-sm mb-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<form method="get" class="search-form">
|
<form method="get">
|
||||||
<div class="row g-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<label for="search" class="form-label fw-semibold">
|
<label for="search" class="block text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
||||||
<i class="fas fa-search me-1"></i>{% trans "Search" %}
|
<i data-lucide="search" class="w-4 h-4"></i>{% trans "Search" %}
|
||||||
</label>
|
</label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
class="form-control"
|
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition"
|
||||||
id="search"
|
id="search"
|
||||||
name="q"
|
name="q"
|
||||||
value="{{ search_query }}"
|
value="{{ search_query }}"
|
||||||
placeholder="{% trans 'Search by name, email, phone...' %}">
|
placeholder="{% trans 'Search by name, email, phone...' %}">
|
||||||
</div>
|
</div>
|
||||||
{% comment %} <div class="col-md-3">
|
|
||||||
<label for="stage" class="form-label fw-semibold">
|
|
||||||
<i class="fas fa-filter me-1"></i>{% trans "Stage" %}
|
|
||||||
</label>
|
|
||||||
<select class="form-select" id="stage" name="stage">
|
|
||||||
<option value="">{% trans "All Stages" %}</option>
|
|
||||||
{% for stage_value, stage_label in stage_choices %}
|
|
||||||
<option value="{{ stage_value }}"
|
|
||||||
{% if stage_filter == stage_value %}selected{% endif %}>
|
|
||||||
{{ stage_label }}
|
|
||||||
</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3 d-flex align-items-end">
|
|
||||||
<button type="submit" class="btn btn-main-action w-100">
|
|
||||||
<i class="fas fa-search me-1"></i> {% trans "Search" %}
|
|
||||||
</button>
|
|
||||||
</div> {% endcomment %}
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Results Summary -->
|
<!-- Results Summary -->
|
||||||
<div class="row mb-3">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||||||
<div class="col-md-6">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<div class="kaauh-card shadow-sm h-100 p-3">
|
<div class="p-6 text-center">
|
||||||
<div class="card-body text-center">
|
<div class="w-12 h-12 bg-temple-red/10 rounded-lg flex items-center justify-center mx-auto mb-3">
|
||||||
<div class="text-primary-theme mb-2">
|
<i data-lucide="users" class="w-6 h-6 text-temple-red"></i>
|
||||||
<i class="fas fa-users fa-2x"></i>
|
|
||||||
</div>
|
</div>
|
||||||
<h4 class="card-title">{{ total_persons }}</h4>
|
<h4 class="text-2xl font-bold text-gray-900 mb-1">{{ total_persons }}</h4>
|
||||||
<p class="card-text text-muted">{% trans "Total Persons" %}</p>
|
<p class="text-gray-600">{% trans "Total Persons" %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
<div class="p-6 text-center">
|
||||||
|
<div class="w-12 h-12 bg-temple-red/10 rounded-lg flex items-center justify-center mx-auto mb-3">
|
||||||
|
<i data-lucide="check-circle" class="w-6 h-6 text-temple-red"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<h4 class="text-2xl font-bold text-gray-900 mb-1">{{ page_obj|length }}</h4>
|
||||||
<div class="kaauh-card shadow-sm h-100 p-3">
|
<p class="text-gray-600">{% trans "Showing on this page" %}</p>
|
||||||
<div class="card-body text-center">
|
|
||||||
<div class="text-primary-theme mb-2">
|
|
||||||
<i class="fas fa-check-circle fa-2x"></i>
|
|
||||||
</div>
|
|
||||||
<h4 class="card-title">{{ page_obj|length }}</h4>
|
|
||||||
<p class="card-text text-muted">{% trans "Showing on this page" %}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Persons Table -->
|
<!-- Persons Table -->
|
||||||
<div class="kaauh-card shadow-sm">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<div class="card-body p-0">
|
<div class="overflow-x-auto">
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<div class="table-responsive person-table">
|
<table class="w-full">
|
||||||
<table class="table table-hover mb-0">
|
<thead class="bg-gray-50 border-b border-gray-200">
|
||||||
<thead class="table-light">
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{% trans "Name" %}</th>
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Name" %}</th>
|
||||||
<th scope="col">{% trans "Email" %}</th>
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Email" %}</th>
|
||||||
<th scope="col">{% trans "Phone" %}</th>
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Phone" %}</th>
|
||||||
<th scope="col">{% trans "Created At" %}</th>
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Created At" %}</th>
|
||||||
{% comment %} <th scope="col">{% trans "Stage" %}</th>
|
<th class="text-center py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Actions" %}</th>
|
||||||
<th scope="col">{% trans "Applied Date" %}</th> {% endcomment %}
|
|
||||||
<th scope="col" class="text-center">{% trans "Actions" %}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for person in page_obj %}
|
{% for person in page_obj %}
|
||||||
<tr class="person-row">
|
<tr class="border-b border-gray-100 hover:bg-gray-50 transition cursor-pointer">
|
||||||
<td>
|
<td class="py-4 px-4">
|
||||||
<div class="d-flex align-items-center">
|
<div class="flex items-center gap-3">
|
||||||
<div class="rounded-circle bg-primary-theme text-white d-flex align-items-center justify-content-center me-2"
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center font-bold text-sm">
|
||||||
style="width: 32px; height: 32px; font-size: 14px; font-weight: 600;">
|
|
||||||
{{ person.first_name|first|upper }}{{ person.last_name|first|upper }}
|
{{ person.first_name|first|upper }}{{ person.last_name|first|upper }}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="fw-semibold">{{ person.first_name }} {{ person.last_name }}</div>
|
<div class="font-semibold text-gray-900">{{ person.first_name }} {{ person.last_name }}</div>
|
||||||
{% if person.address %}
|
{% if person.address %}
|
||||||
<small class="text-muted">{{ person.address|truncatechars:50 }}</small>
|
<div class="text-sm text-gray-500">{{ person.address|truncatechars:50 }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="py-4 px-4">
|
||||||
<a href="mailto:{{ person.email }}" class="text-decoration-none text-dark">
|
<a href="mailto:{{ person.email }}" class="text-gray-900 hover:text-temple-red transition">{{ person.email }}</a>
|
||||||
{{ person.email }}
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ person.phone|default:"-" }}</td>
|
<td class="py-4 px-4">{{ person.phone|default:"-" }}</td>
|
||||||
{% comment %} <td>
|
<td class="py-4 px-4">{{ person.created_at|date:"d-m-Y" }}</td>
|
||||||
<span class="badge bg-light text-dark">
|
<td class="py-4 px-4 text-center">
|
||||||
{{ person.job.title|truncatechars:30 }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% with stage_class=person.stage|lower %}
|
|
||||||
<span class="stage-badge
|
|
||||||
{% if stage_class == 'applied' %}bg-secondary{% endif %}
|
|
||||||
{% if stage_class == 'exam' %}bg-info{% endif %}
|
|
||||||
{% if stage_class == 'interview' %}bg-warning{% endif %}
|
|
||||||
{% if stage_class == 'offer' %}bg-success{% endif %}
|
|
||||||
{% if stage_class == 'hired' %}bg-primary{% endif %}
|
|
||||||
{% if stage_class == 'rejected' %}bg-danger{% endif %}
|
|
||||||
text-white">
|
|
||||||
{{ person.get_stage_display }}
|
|
||||||
</span>
|
|
||||||
{% endwith %}
|
|
||||||
</td> {% endcomment %}
|
|
||||||
<td>{{ person.created_at|date:"d-m-Y" }}</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="button" data-bs-toggle="modal" data-bs-target="#updateModal"
|
<button type="button" data-bs-toggle="modal" data-bs-target="#updateModal"
|
||||||
hx-get="{% url 'person_update' person.slug %}"
|
hx-get="{% url 'person_update' person.slug %}"
|
||||||
hx-target="#updateModalBody"
|
hx-target="#updateModalBody"
|
||||||
hx-swap="outerrHTML"
|
hx-swap="outerrHTML"
|
||||||
hx-select="#person-form"
|
hx-select="#person-form"
|
||||||
hx-vals='{"view":"portal"}'
|
hx-vals='{"view":"portal"}'
|
||||||
class="btn btn-sm btn-outline-secondary"
|
class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition"
|
||||||
title="{% trans 'Edit Person' %}"
|
title="{% trans 'Edit Person' %}">
|
||||||
>
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-5">
|
<div class="text-center py-10">
|
||||||
<i class="fas fa-users fa-3x text-muted mb-3"></i>
|
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<h5 class="text-muted">{% trans "No persons found" %}</h5>
|
<i data-lucide="users" class="w-8 h-8 text-gray-400"></i>
|
||||||
<p class="text-muted">
|
</div>
|
||||||
|
<h5 class="text-gray-600 mb-2">{% trans "No persons found" %}</h5>
|
||||||
|
<p class="text-gray-500 mb-4">
|
||||||
{% if search_query or stage_filter %}
|
{% if search_query or stage_filter %}
|
||||||
{% trans "Try adjusting your search or filter criteria." %}
|
{% trans "Try adjusting your search or filter criteria." %}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -236,8 +130,8 @@
|
|||||||
</p>
|
</p>
|
||||||
{% if not search_query and not stage_filter and agency.assignments.exists %}
|
{% if not search_query and not stage_filter and agency.assignments.exists %}
|
||||||
<a href="{% url 'agency_portal_submit_application_page' agency.assignments.first.slug %}"
|
<a href="{% url 'agency_portal_submit_application_page' agency.assignments.first.slug %}"
|
||||||
class="btn btn-main-action">
|
class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Add First Person" %}
|
<i data-lucide="user-plus" class="w-4 h-4"></i> {% trans "Add First Person" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -247,46 +141,34 @@
|
|||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
{% if page_obj.has_other_pages %}
|
{% if page_obj.has_other_pages %}
|
||||||
<nav aria-label="{% trans 'Persons pagination' %}" class="mt-4">
|
<nav aria-label="{% trans 'Persons pagination' %}" class="mt-6">
|
||||||
<ul class="pagination justify-content-center">
|
<div class="flex justify-center items-center gap-2">
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<li class="page-item">
|
<a href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
||||||
<a class="page-link" href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-angle-double-left"></i>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
||||||
<li class="page-item">
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
|
||||||
<i class="fas fa-angle-left"></i>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for num in page_obj.paginator.page_range %}
|
{% for num in page_obj.paginator.page_range %}
|
||||||
{% if page_obj.number == num %}
|
{% if page_obj.number == num %}
|
||||||
<li class="page-item active">
|
<span class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-temple-red text-white font-semibold">{{ num }}</span>
|
||||||
<span class="page-link">{{ num }}</span>
|
|
||||||
</li>
|
|
||||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||||
<li class="page-item">
|
<a href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">{{ num }}</a>
|
||||||
<a class="page-link" href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">{{ num }}</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<li class="page-item">
|
<a href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
||||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-angle-right"></i>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
<a href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
||||||
<li class="page-item">
|
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
|
||||||
<i class="fas fa-angle-double-right"></i>
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -303,79 +185,77 @@
|
|||||||
<div class="modal-body" id="updateModalBody">
|
<div class="modal-body" id="updateModalBody">
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="d-flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button form="person-form" type="submit" class="btn btn-main-action">
|
<button form="person-form" type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition">
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Update" %}
|
<i data-lucide="save" class="w-4 h-4"></i> {% trans "Update" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Person Modal -->
|
<!-- Person Modal -->
|
||||||
<div class="modal fade modal-lg" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
|
<div class="modal fade" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog modal-lg">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="personModalLabel">
|
<h5 class="modal-title flex items-center gap-2" id="personModalLabel">
|
||||||
<i class="fas fa-users me-2"></i>
|
<i data-lucide="users" class="w-5 h-5"></i>
|
||||||
{% trans "Applicant Details" %}
|
{% trans "Applicant Details" %}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" id="personModalBody">
|
<div class="modal-body" id="personModalBody">
|
||||||
<form id="person_form" method="post" action="{% url 'person_create' %}" >
|
<form id="person_form" method="post" action="{% url 'person_create' %}" >
|
||||||
|
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="view" value="portal">
|
<input type="hidden" name="view" value="portal">
|
||||||
<input type="hidden" name="agency" value="{{ agency.slug }}">
|
<input type="hidden" name="agency" value="{{ agency.slug }}">
|
||||||
|
|
||||||
<div class="row g-4">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div class="col-md-4">
|
<div>
|
||||||
{{ person_form.first_name|as_crispy_field }}
|
{{ person_form.first_name|as_crispy_field }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div>
|
||||||
{{ person_form.middle_name|as_crispy_field }}
|
{{ person_form.middle_name|as_crispy_field }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div>
|
||||||
{{ person_form.last_name|as_crispy_field }}
|
{{ person_form.last_name|as_crispy_field }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.email|as_crispy_field }}
|
{{ person_form.email|as_crispy_field }}
|
||||||
{{person_form.errors}}
|
{{person_form.errors}}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.phone|as_crispy_field }}
|
{{ person_form.phone|as_crispy_field }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.gpa|as_crispy_field }}
|
{{ person_form.gpa|as_crispy_field }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.national_id|as_crispy_field }}
|
{{ person_form.national_id|as_crispy_field }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.date_of_birth|as_crispy_field }}
|
{{ person_form.date_of_birth|as_crispy_field }}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.nationality|as_crispy_field }}
|
{{ person_form.nationality|as_crispy_field }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-4">
|
<div>
|
||||||
<div class="col-12">
|
|
||||||
{{ person_form.address|as_crispy_field }}
|
{{ person_form.address|as_crispy_field }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn btn-main-action" type="submit" form="person_form">{% trans "Save" %}</button>
|
<button class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition" type="submit" form="person_form">{% trans "Save" %}</button>
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
<button type="button" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl transition" data-bs-dismiss="modal">
|
||||||
{% trans "Close" %}
|
{% trans "Close" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -389,36 +269,13 @@ function openPersonModal(personId, personName) {
|
|||||||
document.getElementById('person-modal-text').innerHTML = `<strong>${personName}</strong> (ID: ${personId})`;
|
document.getElementById('person-modal-text').innerHTML = `<strong>${personName}</strong> (ID: ${personId})`;
|
||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
</script>
|
|
||||||
<script>
|
|
||||||
function editPerson(personId) {
|
function editPerson(personId) {
|
||||||
// Placeholder for edit functionality
|
|
||||||
// This would typically open a modal or navigate to edit page
|
|
||||||
console.log('Edit person:', personId);
|
console.log('Edit person:', personId);
|
||||||
// For now, you can redirect to a placeholder edit URL
|
|
||||||
// window.location.href = `/portal/candidates/${personId}/edit/`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-submit form on filter change
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
document.getElementById('stage').addEventListener('change', function() {
|
lucide.createIcons();
|
||||||
this.form.submit();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add row click functionality
|
|
||||||
document.querySelectorAll('.person-row').forEach(row => {
|
|
||||||
row.addEventListener('click', function(e) {
|
|
||||||
// Don't trigger if clicking on buttons or links
|
|
||||||
if (e.target.closest('a, button')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the view details button and click it
|
|
||||||
const viewBtn = this.querySelector('a[title*="View"]');
|
|
||||||
if (viewBtn) {
|
|
||||||
viewBtn.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -11,7 +11,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid">
|
<div class="px-4 py-6">
|
||||||
|
|
||||||
{# Header #}
|
{# Header #}
|
||||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between mb-6">
|
||||||
@ -25,7 +25,7 @@
|
|||||||
<div class="flex flex-col sm:flex-row items-center text-center sm:text-start gap-4">
|
<div class="flex flex-col sm:flex-row items-center text-center sm:text-start gap-4">
|
||||||
<img src="{% if applicant.user.profile_image %}{{ applicant.user.profile_image.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}"
|
<img src="{% if applicant.user.profile_image %}{{ applicant.user.profile_image.url }}{% else %}{% static 'image/default_avatar.png' %}{% endif %}"
|
||||||
alt="{% trans 'Profile Picture' %}"
|
alt="{% trans 'Profile Picture' %}"
|
||||||
class="w-20 h-20 rounded-full border-4 border-kaauh-blue/20 object-cover shadow-lg">
|
class="w-20 h-20 rounded-full border-4 border-temple-red/20 object-cover shadow-lg">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<h3 class="text-lg font-bold text-gray-900 mb-1">{{ applicant.full_name|default:"Applicant Name" }}</h3>
|
<h3 class="text-lg font-bold text-gray-900 mb-1">{{ applicant.full_name|default:"Applicant Name" }}</h3>
|
||||||
<p class="text-sm text-gray-600">{{ applicant.email }}</p>
|
<p class="text-sm text-gray-600">{{ applicant.email }}</p>
|
||||||
@ -39,19 +39,19 @@
|
|||||||
{# Tab Navigation #}
|
{# Tab Navigation #}
|
||||||
<div class="border-b border-gray-200 overflow-x-auto">
|
<div class="border-b border-gray-200 overflow-x-auto">
|
||||||
<div class="flex min-w-max px-4 pt-4 gap-2">
|
<div class="flex min-w-max px-4 pt-4 gap-2">
|
||||||
<button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-50 transition"
|
<button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-temple-red text-temple-red bg-temple-red/5 transition"
|
||||||
onclick="showTab('profile-details')"
|
onclick="showTab('profile-details')"
|
||||||
data-tab="profile-details">
|
data-tab="profile-details">
|
||||||
<i data-lucide="user-circle" class="w-4 h-4 mr-2 inline"></i>
|
<i data-lucide="user-circle" class="w-4 h-4 mr-2 inline"></i>
|
||||||
{% trans "Profile Details" %}
|
{% trans "Profile Details" %}
|
||||||
</button>
|
</button>
|
||||||
<button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-50 transition"
|
<button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:border-temple-red hover:text-temple-red hover:bg-temple-red/5 transition"
|
||||||
onclick="showTab('applications-history')"
|
onclick="showTab('applications-history')"
|
||||||
data-tab="applications-history">
|
data-tab="applications-history">
|
||||||
<i data-lucide="list-alt" class="w-4 h-4 mr-2 inline"></i>
|
<i data-lucide="list-alt" class="w-4 h-4 mr-2 inline"></i>
|
||||||
{% trans "My Applications" %}
|
{% trans "My Applications" %}
|
||||||
</button>
|
</button>
|
||||||
<button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-50 transition"
|
<button class="tab-btn px-4 py-2 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:border-temple-red hover:text-temple-red hover:bg-temple-red/5 transition"
|
||||||
onclick="showTab('document-management')"
|
onclick="showTab('document-management')"
|
||||||
data-tab="document-management">
|
data-tab="document-management">
|
||||||
<i data-lucide="file-upload" class="w-4 h-4 mr-2 inline"></i>
|
<i data-lucide="file-upload" class="w-4 h-4 mr-2 inline"></i>
|
||||||
@ -69,20 +69,20 @@
|
|||||||
<!-- Basic Information Section -->
|
<!-- Basic Information Section -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
||||||
<i data-lucide="user" class="w-5 h-5 text-kaauh-blue"></i>
|
<i data-lucide="user" class="w-5 h-5 text-temple-red"></i>
|
||||||
{% trans "Basic Information" %}
|
{% trans "Basic Information" %}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="bg-gray-50 rounded-xl p-4 space-y-3">
|
<div class="bg-gray-50 rounded-xl p-4 space-y-3">
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="badge" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="badge" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "First Name" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "First Name" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.first_name|default:"" }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.first_name|default:"" }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="badge" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="badge" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "Last Name" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "Last Name" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.last_name|default:"" }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.last_name|default:"" }}</span>
|
||||||
@ -90,7 +90,7 @@
|
|||||||
{% if applicant.middle_name %}
|
{% if applicant.middle_name %}
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="badge" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="badge" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "Middle Name" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "Middle Name" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.middle_name }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.middle_name }}</span>
|
||||||
@ -98,7 +98,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="mail" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="mail" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "Email" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "Email" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.email|default:"" }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.email|default:"" }}</span>
|
||||||
@ -109,13 +109,13 @@
|
|||||||
<!-- Contact Information Section -->
|
<!-- Contact Information Section -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
||||||
<i data-lucide="address-book" class="w-5 h-5 text-kaauh-blue"></i>
|
<i data-lucide="address-book" class="w-5 h-5 text-temple-red"></i>
|
||||||
{% trans "Contact Information" %}
|
{% trans "Contact Information" %}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="bg-gray-50 rounded-xl p-4 space-y-3">
|
<div class="bg-gray-50 rounded-xl p-4 space-y-3">
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="phone" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="phone" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "Phone" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "Phone" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.phone|default:"" }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.phone|default:"" }}</span>
|
||||||
@ -123,7 +123,7 @@
|
|||||||
{% if applicant.address %}
|
{% if applicant.address %}
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="map-pin" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="map-pin" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "Address" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "Address" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium text-right max-w-xs">{{ applicant.address|linebreaksbr }}</span>
|
<span class="text-gray-900 font-medium text-right max-w-xs">{{ applicant.address|linebreaksbr }}</span>
|
||||||
@ -132,10 +132,10 @@
|
|||||||
{% if applicant.linkedin_profile %}
|
{% if applicant.linkedin_profile %}
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="linkedin" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="linkedin" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "LinkedIn Profile" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "LinkedIn Profile" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ applicant.linkedin_profile }}" target="_blank" class="text-kaauh-blue hover:text-kaauh-blue/80 font-medium flex items-center gap-1">
|
<a href="{{ applicant.linkedin_profile }}" target="_blank" class="text-temple-red hover:text-temple-red-dark font-medium flex items-center gap-1">
|
||||||
{% trans "View Profile" %} <i data-lucide="external-link" class="w-3 h-3"></i>
|
{% trans "View Profile" %} <i data-lucide="external-link" class="w-3 h-3"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -146,27 +146,27 @@
|
|||||||
<!-- Personal Details Section -->
|
<!-- Personal Details Section -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
||||||
<i data-lucide="user-circle" class="w-5 h-5 text-kaauh-blue"></i>
|
<i data-lucide="user-circle" class="w-5 h-5 text-temple-red"></i>
|
||||||
{% trans "Personal Details" %}
|
{% trans "Personal Details" %}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="bg-gray-50 rounded-xl p-4 space-y-3">
|
<div class="bg-gray-50 rounded-xl p-4 space-y-3">
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="calendar" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="calendar" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "Date of Birth" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "Date of Birth" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.date_of_birth|date:"M d, Y"|default:"" }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.date_of_birth|date:"M d, Y"|default:"" }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="venus-mars" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="venus-mars" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "Gender" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "Gender" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.get_gender_display|default:"" }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.get_gender_display|default:"" }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="globe" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="globe" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "Nationality" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "Nationality" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.get_nationality_display|default:"" }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.get_nationality_display|default:"" }}</span>
|
||||||
@ -177,14 +177,14 @@
|
|||||||
<!-- Professional Information Section -->
|
<!-- Professional Information Section -->
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
||||||
<i data-lucide="briefcase" class="w-5 h-5 text-kaauh-blue"></i>
|
<i data-lucide="briefcase" class="w-5 h-5 text-temple-red"></i>
|
||||||
{% trans "Professional Information" %}
|
{% trans "Professional Information" %}
|
||||||
</h4>
|
</h4>
|
||||||
<div class="bg-gray-50 rounded-xl p-4 space-y-3">
|
<div class="bg-gray-50 rounded-xl p-4 space-y-3">
|
||||||
{% if applicant.user.designation %}
|
{% if applicant.user.designation %}
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="user-tie" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="user-tie" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "Designation" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "Designation" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.user.designation }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.user.designation }}</span>
|
||||||
@ -193,7 +193,7 @@
|
|||||||
{% if applicant.gpa %}
|
{% if applicant.gpa %}
|
||||||
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
<div class="flex justify-between items-start pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<div class="flex items-center gap-2 text-gray-600">
|
<div class="flex items-center gap-2 text-gray-600">
|
||||||
<i data-lucide="graduation-cap" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="graduation-cap" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span class="font-semibold text-gray-700">{% trans "GPA" %}</span>
|
<span class="font-semibold text-gray-700">{% trans "GPA" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-gray-900 font-medium">{{ applicant.gpa }}</span>
|
<span class="text-gray-900 font-medium">{{ applicant.gpa }}</span>
|
||||||
@ -206,20 +206,20 @@
|
|||||||
{# Applications History Tab #}
|
{# Applications History Tab #}
|
||||||
<div id="applications-history" class="tab-content hidden">
|
<div id="applications-history" class="tab-content hidden">
|
||||||
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
||||||
<i data-lucide="list-checks" class="w-5 h-5 text-kaauh-blue"></i>
|
<i data-lucide="list-checks" class="w-5 h-5 text-temple-red"></i>
|
||||||
{% trans "Application Tracking" %}
|
{% trans "Application Tracking" %}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
{% if applications %}
|
{% if applications %}
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{% for application in applications %}
|
{% for application in applications %}
|
||||||
<div class="bg-white rounded-xl border border-gray-200 shadow-sm hover:shadow-md hover:border-kaauh-blue/30 transition-all duration-300">
|
<div class="bg-white rounded-xl border border-gray-200 shadow-sm hover:shadow-md hover:border-temple-red/30 transition-all duration-300">
|
||||||
<div class="p-5 flex flex-col h-full">
|
<div class="p-5 flex flex-col h-full">
|
||||||
<!-- Job Title -->
|
<!-- Job Title -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<h5 class="font-bold text-gray-900 mb-2">
|
<h5 class="font-bold text-gray-900 mb-2">
|
||||||
<a href="{% url 'applicant_application_detail' application.slug %}"
|
<a href="{% url 'applicant_application_detail' application.slug %}"
|
||||||
class="text-kaauh-blue hover:text-kaauh-blue/80 transition">
|
class="text-temple-red hover:text-temple-red-dark transition">
|
||||||
{{ application.job.title }}
|
{{ application.job.title }}
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
@ -233,7 +233,7 @@
|
|||||||
<div class="space-y-2 mb-4">
|
<div class="space-y-2 mb-4">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="text-xs text-gray-600 font-medium">{% trans "Current Stage" %}</span>
|
<span class="text-xs text-gray-600 font-medium">{% trans "Current Stage" %}</span>
|
||||||
<span class="text-xs font-bold px-2 py-1 rounded-full bg-kaauh-blue text-white">
|
<span class="text-xs font-bold px-2 py-1 rounded-full bg-temple-red text-white">
|
||||||
{{ application.stage }}
|
{{ application.stage }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -250,7 +250,7 @@
|
|||||||
<!-- Action Button -->
|
<!-- Action Button -->
|
||||||
<div class="mt-auto">
|
<div class="mt-auto">
|
||||||
<a href="{% url 'applicant_application_detail' application.slug %}"
|
<a href="{% url 'applicant_application_detail' application.slug %}"
|
||||||
class="inline-flex items-center justify-center gap-2 w-full bg-kaauh-blue hover:bg-[#004f57] text-white font-medium py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
|
class="inline-flex items-center justify-center gap-2 w-full bg-temple-red hover:bg-temple-red-dark text-white font-medium py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
|
||||||
<i data-lucide="eye" class="w-4 h-4"></i>
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
{% trans "View Details" %}
|
{% trans "View Details" %}
|
||||||
</a>
|
</a>
|
||||||
@ -260,10 +260,10 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="bg-kaauh-blue/5 border-2 border-dashed border-kaauh-blue/30 rounded-2xl p-8 text-center">
|
<div class="bg-temple-red/5 border-2 border-dashed border-temple-red/30 rounded-2xl p-8 text-center">
|
||||||
<i data-lucide="info" class="w-12 h-12 text-kaauh-blue mx-auto mb-4"></i>
|
<i data-lucide="info" class="w-12 h-12 text-temple-red mx-auto mb-4"></i>
|
||||||
<h5 class="mb-3 font-bold text-gray-900">{% trans "You haven't submitted any applications yet." %}</h5>
|
<h5 class="mb-3 font-bold text-gray-900">{% trans "You haven't submitted any applications yet." %}</h5>
|
||||||
<a href="{% url 'kaauh_career' %}" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-6 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
|
<a href="{% url 'kaauh_career' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-red-dark text-white font-medium px-6 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md">
|
||||||
{% trans "View Available Jobs" %} <i data-lucide="arrow-right" class="w-4 h-4"></i>
|
{% trans "View Available Jobs" %} <i data-lucide="arrow-right" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -273,7 +273,7 @@
|
|||||||
{# Document Management Tab #}
|
{# Document Management Tab #}
|
||||||
<div id="document-management" class="tab-content hidden">
|
<div id="document-management" class="tab-content hidden">
|
||||||
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
<h4 class="mb-4 font-bold text-gray-900 flex items-center gap-2">
|
||||||
<i data-lucide="file-text" class="w-5 h-5 text-kaauh-blue"></i>
|
<i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
|
||||||
{% trans "My Uploaded Documents" %}
|
{% trans "My Uploaded Documents" %}
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
@ -281,7 +281,7 @@
|
|||||||
{% trans "You can upload and manage your resume, certificates, and professional documents here. These documents will be attached to your applications." %}
|
{% trans "You can upload and manage your resume, certificates, and professional documents here. These documents will be attached to your applications." %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<button type="button" class="inline-flex items-center gap-2 bg-kaauh-blue hover:bg-[#004f57] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md mb-6"
|
<button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-temple-red-dark text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-sm hover:shadow-md mb-6"
|
||||||
onclick="document.getElementById('documentUploadModal').classList.remove('hidden')">
|
onclick="document.getElementById('documentUploadModal').classList.remove('hidden')">
|
||||||
<i data-lucide="upload-cloud" class="w-4 h-4"></i>
|
<i data-lucide="upload-cloud" class="w-4 h-4"></i>
|
||||||
{% trans "Upload New Document" %}
|
{% trans "Upload New Document" %}
|
||||||
@ -296,14 +296,14 @@
|
|||||||
id="document-{{ document.id }}">
|
id="document-{{ document.id }}">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center gap-2 font-semibold text-gray-900">
|
<div class="flex items-center gap-2 font-semibold text-gray-900">
|
||||||
<i data-lucide="file" class="w-4 h-4 text-kaauh-blue"></i>
|
<i data-lucide="file" class="w-4 h-4 text-temple-red"></i>
|
||||||
<span>{{ document.document_type|title }}</span>
|
<span>{{ document.document_type|title }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-xs text-gray-500">({{ document.file.name|split:"/"|last }})</span>
|
<span class="text-xs text-gray-500">({{ document.file.name|split:"/"|last }})</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<span class="text-xs text-gray-500">{% trans "Uploaded:" %} {{ document.uploaded_at|date:"d M Y" }}</span>
|
<span class="text-xs text-gray-500">{% trans "Uploaded:" %} {{ document.uploaded_at|date:"d M Y" }}</span>
|
||||||
<a href="{{ document.file.url }}" target="_blank" class="text-gray-600 hover:text-kaauh-blue transition">
|
<a href="{{ document.file.url }}" target="_blank" class="text-gray-600 hover:text-temple-red transition">
|
||||||
<i data-lucide="eye" class="w-4 h-4"></i>
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
<button hx-post="{% url 'document_delete' document.pk %}"
|
<button hx-post="{% url 'document_delete' document.pk %}"
|
||||||
@ -356,7 +356,7 @@
|
|||||||
|
|
||||||
// Remove active state from all tabs
|
// Remove active state from all tabs
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
btn.classList.remove('border-kaauh-blue', 'text-kaauh-blue', 'bg-kaauh-blue/5');
|
btn.classList.remove('border-temple-red', 'text-temple-red', 'bg-temple-red/5');
|
||||||
btn.classList.add('border-transparent', 'text-gray-600');
|
btn.classList.add('border-transparent', 'text-gray-600');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -365,7 +365,7 @@
|
|||||||
|
|
||||||
// Add active state to selected tab
|
// Add active state to selected tab
|
||||||
const activeBtn = document.querySelector(`[data-tab="${tabId}"]`);
|
const activeBtn = document.querySelector(`[data-tab="${tabId}"]`);
|
||||||
activeBtn.classList.add('border-kaauh-blue', 'text-kaauh-blue', 'bg-kaauh-blue/5');
|
activeBtn.classList.add('border-temple-red', 'text-temple-red', 'bg-temple-red/5');
|
||||||
activeBtn.classList.remove('border-transparent', 'text-gray-600');
|
activeBtn.classList.remove('border-transparent', 'text-gray-600');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,177 +4,21 @@
|
|||||||
{% block title %}{% trans "Create Account" %}{% endblock %}
|
{% block title %}{% trans "Create Account" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<style>
|
<div class="min-h-[calc(100vh-80px)] flex justify-center bg-gray-50 py-16 px-5">
|
||||||
/* ---------------------------------------------------------------------- */
|
<div class="w-full max-w-3xl bg-white rounded-2xl shadow-lg overflow-hidden border border-gray-200">
|
||||||
/* MODERN KAAUH DESIGN SYSTEM */
|
<!-- Header -->
|
||||||
/* ---------------------------------------------------------------------- */
|
<div class="bg-gradient-to-r from-temple-red to-[#7a1a29] text-white p-8 text-center">
|
||||||
:root {
|
<h2 class="text-2xl font-bold flex items-center justify-center gap-3">
|
||||||
--kaauh-teal: #00636e;
|
<i data-lucide="user-plus" class="w-7 h-7"></i>
|
||||||
--kaauh-teal-dark: #004a53;
|
{% trans "Create Account" %}
|
||||||
--kaauh-teal-light: #f0f7f8;
|
</h2>
|
||||||
--error-red: #e74c3c;
|
|
||||||
--border-color: #e2e8f0;
|
|
||||||
--text-dark: #2d3436;
|
|
||||||
--text-muted: #636e72;
|
|
||||||
--shadow: 0 10px 25px rgba(0, 0, 0, 0.08);
|
|
||||||
--radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page Wrapper */
|
|
||||||
.register-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 60px 20px;
|
|
||||||
min-height: calc(100vh - 80px);
|
|
||||||
background-color: #f8fafc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom Card */
|
|
||||||
.kaauh-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 800px;
|
|
||||||
background: #ffffff;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.kaauh-header {
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 30px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.kaauh-header h2 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Layout - Grid System */
|
|
||||||
.kaauh-body {
|
|
||||||
padding: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(12, 1fr);
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-4 { grid-column: span 4; }
|
|
||||||
.grid-6 { grid-column: span 6; }
|
|
||||||
.grid-8 { grid-column: span 8; }
|
|
||||||
.grid-12 { grid-column: span 12; }
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.grid-4, .grid-6, .grid-8 { grid-column: span 12; }
|
|
||||||
.kaauh-body { padding: 25px; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Pure CSS Form Styling */
|
|
||||||
.field-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-label {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--text-dark);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.required { color: var(--error-red); }
|
|
||||||
|
|
||||||
/* Target Django default inputs */
|
|
||||||
input, select, textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
font-size: 1rem;
|
|
||||||
border: 2px solid var(--border-color);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: #ffffff;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus, select:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
box-shadow: 0 0 0 4px rgba(0, 99, 110, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Error Styling */
|
|
||||||
.error-text {
|
|
||||||
color: var(--error-red);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button Styling */
|
|
||||||
.btn-submit {
|
|
||||||
width: 100%;
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal) 0%, var(--kaauh-teal-dark) 100%);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 16px;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 20px rgba(0, 99, 110, 0.3);
|
|
||||||
filter: brightness(1.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Alerts */
|
|
||||||
.alert {
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.alert-error, .alert-danger { background: #fee2e2; color: #b91c1c; border: 1px solid #fecaca; }
|
|
||||||
.alert-success { background: #dcfce7; color: #15803d; border: 1px solid #bbf7d0; }
|
|
||||||
|
|
||||||
/* Footer */
|
|
||||||
.kaauh-footer {
|
|
||||||
padding: 20px;
|
|
||||||
background: #f8fafc;
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-teal {
|
|
||||||
color: var(--kaauh-teal);
|
|
||||||
font-weight: 700;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.link-teal:hover { text-decoration: underline; }
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="register-wrapper">
|
|
||||||
<div class="kaauh-card">
|
|
||||||
<div class="kaauh-header">
|
|
||||||
<h2><i class="fas fa-user-plus"></i> {% trans "Create Account" %}</h2>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kaauh-body">
|
<!-- Body -->
|
||||||
|
<div class="p-10">
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-{{ message.tags }}">
|
<div class="{% if 'error' in message.tags or 'danger' in message.tags %}bg-red-50 border border-red-200 text-red-800{% elif 'success' in message.tags %}bg-green-50 border border-green-200 text-green-800{% else %}bg-blue-50 border border-blue-200 text-blue-800{% endif %} rounded-xl p-4 mb-6 font-semibold text-sm">
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -183,83 +27,83 @@
|
|||||||
<form method="post" novalidate>
|
<form method="post" novalidate>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="form-grid">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-5">
|
||||||
<div class="field-group grid-4">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "First Name" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "First Name" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.first_name }}
|
{{ form.first_name }}
|
||||||
{% if form.first_name.errors %}<div class="error-text">{{ form.first_name.errors.0 }}</div>{% endif %}
|
{% if form.first_name.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.first_name.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-4">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "Middle Name" %}</label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "Middle Name" %}</label>
|
||||||
{{ form.middle_name }}
|
{{ form.middle_name }}
|
||||||
{% if form.middle_name.errors %}<div class="error-text">{{ form.middle_name.errors.0 }}</div>{% endif %}
|
{% if form.middle_name.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.middle_name.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-4">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "Last Name" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "Last Name" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.last_name }}
|
{{ form.last_name }}
|
||||||
{% if form.last_name.errors %}<div class="error-text">{{ form.last_name.errors.0 }}</div>{% endif %}
|
{% if form.last_name.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.last_name.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-8">
|
<div class="md:col-span-2 space-y-2">
|
||||||
<label class="field-label">{% trans "Email Address" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "Email Address" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.email }}
|
{{ form.email }}
|
||||||
{% if form.email.errors %}<div class="error-text">{{ form.email.errors.0 }}</div>{% endif %}
|
{% if form.email.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.email.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-4">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "Phone Number" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "Phone Number" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.phone }}
|
{{ form.phone }}
|
||||||
{% if form.phone.errors %}<div class="error-text">{{ form.phone.errors.0 }}</div>{% endif %}
|
{% if form.phone.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.phone.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-6">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "GPA" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "GPA" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.gpa }}
|
{{ form.gpa }}
|
||||||
{% if form.gpa.errors %}<div class="error-text">{{ form.gpa.errors.0 }}</div>{% endif %}
|
{% if form.gpa.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.gpa.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-6">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "National ID / Iqama" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "National ID / Iqama" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.national_id }}
|
{{ form.national_id }}
|
||||||
{% if form.national_id.errors %}<div class="error-text">{{ form.national_id.errors.0 }}</div>{% endif %}
|
{% if form.national_id.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.national_id.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-6">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "Nationality" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "Nationality" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.nationality }}
|
{{ form.nationality }}
|
||||||
{% if form.nationality.errors %}<div class="error-text">{{ form.nationality.errors.0 }}</div>{% endif %}
|
{% if form.nationality.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.nationality.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-6">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "Gender" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "Gender" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.gender }}
|
{{ form.gender }}
|
||||||
{% if form.gender.errors %}<div class="error-text">{{ form.gender.errors.0 }}</div>{% endif %}
|
{% if form.gender.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.gender.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-6">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "Password" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "Password" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.password }}
|
{{ form.password }}
|
||||||
{% if form.password.errors %}<div class="error-text">{{ form.password.errors.0 }}</div>{% endif %}
|
{% if form.password.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.password.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field-group grid-6">
|
<div class="space-y-2">
|
||||||
<label class="field-label">{% trans "Confirm Password" %} <span class="required">*</span></label>
|
<label class="block text-sm font-bold text-gray-900">{% trans "Confirm Password" %} <span class="text-red-500">*</span></label>
|
||||||
{{ form.confirm_password }}
|
{{ form.confirm_password }}
|
||||||
{% if form.confirm_password.errors %}<div class="error-text">{{ form.confirm_password.errors.0 }}</div>{% endif %}
|
{% if form.confirm_password.errors %}<div class="text-sm font-bold text-red-600 mt-1">{{ form.confirm_password.errors.0 }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if form.non_field_errors %}
|
{% if form.non_field_errors %}
|
||||||
<div class="grid-12">
|
<div class="md:col-span-3">
|
||||||
<div class="alert alert-danger">
|
<div class="bg-red-50 border border-red-200 text-red-800 rounded-xl p-4 font-semibold text-sm">
|
||||||
{% for error in form.non_field_errors %}{{ error }}{% endfor %}
|
{% for error in form.non_field_errors %}{{ error }}{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="grid-12">
|
<div class="md:col-span-3">
|
||||||
<button type="submit" class="btn-submit">
|
<button type="submit" class="w-full bg-gradient-to-r from-temple-red to-[#7a1a29] text-white font-bold text-lg py-4 rounded-xl hover:shadow-lg hover:-translate-y-0.5 transition-all duration-300">
|
||||||
{% trans "Create Account" %}
|
{% trans "Create Account" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -267,12 +111,39 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kaauh-footer">
|
<!-- Footer -->
|
||||||
|
<div class="bg-gray-50 border-t border-gray-200 p-5 text-center text-base">
|
||||||
{% trans "Already have an account?" %}
|
{% trans "Already have an account?" %}
|
||||||
<a href="{% url 'account_login' %}?next={% url 'application_submit_form' job.slug %}" class="link-teal">
|
<a href="{% url 'account_login' %}?next={% url 'application_submit_form' job.slug %}" class="font-bold text-temple-red hover:underline ml-1">
|
||||||
{% trans "Login here" %}
|
{% trans "Login here" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Form input styling to match theme */
|
||||||
|
input, select, textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 1rem;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus, select:focus, textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #9d2235;
|
||||||
|
box-shadow: 0 0 0 4px rgba(157, 34, 53, 0.1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
lucide.createIcons();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -3,202 +3,277 @@
|
|||||||
|
|
||||||
{% block title %}{% trans "Create Application" %} - {{ block.super }}{% endblock %}
|
{% block title %}{% trans "Create Application" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* ================================================= */
|
|
||||||
/* THEME VARIABLES AND GLOBAL STYLES (FROM JOB DETAIL) */
|
|
||||||
/* ================================================= */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Primary Color Overrides */
|
|
||||||
.text-primary { color: var(--kaauh-teal) !important; }
|
|
||||||
|
|
||||||
/* Main Action Button Style */
|
|
||||||
.btn-main-action, .btn-primary {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.6rem 1.2rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover, .btn-primary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Outlined Button Styles */
|
|
||||||
.btn-secondary, .btn-outline-secondary {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border: 1px solid var(--kaauh-teal);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.btn-secondary:hover, .btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card enhancements */
|
|
||||||
.card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Colored Header Card */
|
|
||||||
.candidate-header-card {
|
|
||||||
background: linear-gradient(135deg, var(--kaauh-teal), #004d57);
|
|
||||||
color: white;
|
|
||||||
border-radius: 0.75rem 0.75rem 0 0;
|
|
||||||
padding: 1.5rem;
|
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
.candidate-header-card h1 {
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.8rem;
|
|
||||||
}
|
|
||||||
.heroicon {
|
|
||||||
width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
vertical-align: text-bottom;
|
|
||||||
stroke: currentColor;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="px-4 py-6">
|
||||||
|
<!-- Header Card -->
|
||||||
<div class="card mb-4">
|
<div class="bg-gradient-to-br from-temple-red to-red-800 rounded-xl shadow-xl p-6 mb-6 text-white">
|
||||||
<div class="candidate-header-card">
|
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||||
<div class="d-flex justify-content-between align-items-start flex-wrap">
|
<div class="flex-1">
|
||||||
<div class="flex-grow-1">
|
<h1 class="text-3xl font-bold mb-2 flex items-center gap-2">
|
||||||
<h1 class="h3 mb-1">
|
<i data-lucide="user-plus" class="w-8 h-8"></i>
|
||||||
<i class="fas fa-user-plus"></i>
|
|
||||||
{% trans "Create New Application" %}
|
{% trans "Create New Application" %}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-white opacity-75 mb-0">{% trans "Enter details to create a new application record." %}</p>
|
<p class="text-red-100 text-lg">{% trans "Enter details to create a new application record." %}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2 mt-1">
|
<div class="flex gap-2">
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#personModal">
|
<button type="button" class="modal-trigger bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2" data-modal="personModal">
|
||||||
<i class="fas fa-user-plus me-1"></i>
|
<i data-lucide="user-plus" class="w-4 h-4"></i>
|
||||||
<span class="d-none d-sm-inline">{% trans "Create New Applicant" %}</span>
|
<span class="hidden sm:inline">{% trans "Create New Applicant" %}</span>
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'application_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
|
<a href="{% url 'application_list' %}" class="bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2" title="{% trans 'Back to List' %}">
|
||||||
<i class="fas fa-arrow-left"></i>
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
<span class="d-none d-sm-inline">{% trans "Back to List" %}</span>
|
<span class="hidden sm:inline">{% trans "Back to List" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<!-- Form Card -->
|
||||||
<div class="card-header bg-white border-bottom">
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-gray-200">
|
||||||
<h2 class="h5 mb-0 text-primary">
|
<div class="px-6 py-4 border-b border-gray-200 bg-gray-50">
|
||||||
<i class="fas fa-file-alt me-1"></i>
|
<h2 class="text-xl font-semibold text-temple-dark flex items-center gap-2">
|
||||||
|
<i data-lucide="file-text" class="w-5 h-5"></i>
|
||||||
{% trans "Application Information" %}
|
{% trans "Application Information" %}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<form method="post" enctype="multipart/form-data">
|
<form method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{# Split form into two columns for better horizontal use #}
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="row g-4">
|
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ field|as_crispy_field }}
|
<label for="{{ field.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ field.label }}
|
||||||
|
{% if field.field.required %}<span class="text-red-500">*</span>{% endif %}
|
||||||
|
</label>
|
||||||
|
{{ field }}
|
||||||
|
{% if field.help_text %}
|
||||||
|
<p class="text-sm text-gray-500 mt-1">{{ field.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% for error in field.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr class="mt-4 mb-4">
|
<div class="border-t border-gray-200 mt-6 pt-6">
|
||||||
<button class="btn btn-main-action" type="submit">
|
<button type="submit" class="bg-temple-red hover:bg-red-800 text-white font-semibold px-8 py-3 rounded-xl transition shadow-md hover:shadow-lg flex items-center gap-2">
|
||||||
<i class="fas fa-save me-1"></i>
|
<i data-lucide="save" class="w-5 h-5"></i>
|
||||||
{% trans "Create Application" %}
|
{% trans "Create Application" %}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal -->
|
<!-- Modal -->
|
||||||
<div class="modal fade modal-lg" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
|
<div class="hidden fixed inset-0 z-50 overflow-y-auto" id="personModal" role="dialog" aria-labelledby="personModalLabel">
|
||||||
<div class="modal-dialog">
|
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||||
<div class="modal-content">
|
<div class="fixed inset-0 bg-black/50 transition-opacity" aria-hidden="true"></div>
|
||||||
<div class="modal-header">
|
<span class="hidden sm:inline-block sm:align-middle sm:h-screen sm:align-middle" aria-hidden="true">​</span>
|
||||||
<h5 class="modal-title" id="personModalLabel">
|
<div class="inline-block align-bottom bg-white rounded-2xl text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-3xl sm:w-full">
|
||||||
<i class="fas fa-question-circle me-2"></i>{% trans "Help" %}
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 border-b border-gray-200 flex justify-between items-center">
|
||||||
</h5>
|
<h3 class="text-lg font-semibold text-gray-900 flex items-center gap-2" id="personModalLabel">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<i data-lucide="help-circle" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
{% trans "Create New Applicant" %}
|
||||||
|
</h3>
|
||||||
|
<button type="button" class="modal-close-btn text-gray-400 hover:text-gray-600 transition">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="px-4 pt-5 pb-4 sm:p-6">
|
||||||
<form id="person_form" hx-post="{% url 'person_create' %}" hx-vals='{"view":"job"}' hx-target="#div_id_person" hx-select="#div_id_person" hx-swap="outerHTML">
|
<form id="person_form" hx-post="{% url 'person_create' %}" hx-vals='{"view":"job"}' hx-target="#div_id_person" hx-select="#div_id_person" hx-swap="outerHTML">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row g-4">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div class="col-md-4">
|
<div>
|
||||||
{{ person_form.first_name|as_crispy_field }}
|
<label for="{{ person_form.first_name.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.first_name.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.first_name }}
|
||||||
|
{% for error in person_form.first_name.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div>
|
||||||
{{ person_form.middle_name|as_crispy_field }}
|
<label for="{{ person_form.middle_name.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.middle_name.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.middle_name }}
|
||||||
|
{% for error in person_form.middle_name.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div>
|
||||||
{{ person_form.last_name|as_crispy_field }}
|
<label for="{{ person_form.last_name.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.last_name.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.last_name }}
|
||||||
|
{% for error in person_form.last_name.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.email|as_crispy_field }}
|
<label for="{{ person_form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.email.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.email }}
|
||||||
|
{% for error in person_form.email.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.phone|as_crispy_field }}
|
<label for="{{ person_form.phone.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.phone.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.phone }}
|
||||||
|
{% for error in person_form.phone.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.gpa|as_crispy_field }}
|
<label for="{{ person_form.gpa.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.gpa.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.gpa }}
|
||||||
|
{% for error in person_form.gpa.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.national_id|as_crispy_field }}
|
<label for="{{ person_form.national_id.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.national_id.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.national_id }}
|
||||||
|
{% for error in person_form.national_id.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.date_of_birth|as_crispy_field }}
|
<label for="{{ person_form.date_of_birth.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.date_of_birth.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.date_of_birth }}
|
||||||
|
{% for error in person_form.date_of_birth.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.nationality|as_crispy_field }}
|
<label for="{{ person_form.nationality.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.nationality.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.nationality }}
|
||||||
|
{% for error in person_form.nationality.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row g-4">
|
<div class="mt-4">
|
||||||
<div class="col-12">
|
<label for="{{ person_form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
{{ person_form.address|as_crispy_field }}
|
{{ person_form.address.label }}
|
||||||
</div>
|
</label>
|
||||||
|
{{ person_form.address }}
|
||||||
|
{% for error in person_form.address.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
|
||||||
<button type="submit" class="btn btn-main-action" data-bs-dismiss="modal" form="person_form">{% trans "Save" %}</button>
|
<button type="submit" class="modal-save-btn w-full inline-flex justify-center rounded-xl border border-transparent shadow-sm px-4 py-2 bg-temple-red text-base font-medium text-white hover:bg-red-800 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
<i data-lucide="save" class="w-4 h-4 mr-2"></i>{% trans "Save" %}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="modal-close-btn mt-3 w-full inline-flex justify-center rounded-xl border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
|
||||||
|
<i data-lucide="x" class="w-4 h-4 mr-2"></i>{% trans "Close" %}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block customJS %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize Lucide icons
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
|
// Modal functionality
|
||||||
|
const modal = document.getElementById('personModal');
|
||||||
|
let isModalOpen = false;
|
||||||
|
|
||||||
|
// Open modal buttons
|
||||||
|
const openModalBtns = document.querySelectorAll('.modal-trigger');
|
||||||
|
openModalBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
isModalOpen = true;
|
||||||
|
setTimeout(() => lucide.createIcons(), 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal buttons
|
||||||
|
document.querySelectorAll('.modal-close-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (modal && isModalOpen) {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal when clicking outside
|
||||||
|
if (modal) {
|
||||||
|
modal.addEventListener('click', function(e) {
|
||||||
|
if (e.target === modal && isModalOpen) {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close modal on escape key
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && modal && isModalOpen) {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add form styling
|
||||||
|
const formInputs = document.querySelectorAll('input, select, textarea');
|
||||||
|
formInputs.forEach(input => {
|
||||||
|
input.classList.add('w-full', 'px-3', 'py-2.5', 'border', 'border-gray-300', 'rounded-lg', 'focus:ring-2', 'focus:ring-temple-red', 'focus:border-transparent', 'transition');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reinitialize Lucide icons after HTMX updates
|
||||||
|
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
|
// Re-apply form styling to new elements
|
||||||
|
const newFormInputs = evt.detail.xhr.response.querySelectorAll ?
|
||||||
|
evt.detail.xhr.response.querySelectorAll('input, select, textarea') : [];
|
||||||
|
newFormInputs.forEach(input => {
|
||||||
|
input.classList.add('w-full', 'px-3', 'py-2.5', 'border', 'border-gray-300', 'rounded-lg', 'focus:ring-2', 'focus:ring-temple-red', 'focus:border-transparent', 'transition');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -3,269 +3,99 @@
|
|||||||
|
|
||||||
{% block title %}{% trans "Delete Application" %} - {{ block.super }}{% endblock %}
|
{% block title %}{% trans "Delete Application" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* KAAT-S UI Variables */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-danger: #dc3545;
|
|
||||||
--kaauh-warning: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Container & Card Styling */
|
|
||||||
.kaauh-card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Warning Section */
|
|
||||||
.warning-section {
|
|
||||||
background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%);
|
|
||||||
border: 1px solid #ffeeba;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 2rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-icon {
|
|
||||||
font-size: 4rem;
|
|
||||||
color: var(--kaauh-warning);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-title {
|
|
||||||
color: #856404;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-text {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Info Card */
|
|
||||||
.app-info {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-bottom: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-right: 1rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-content {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
color: #6c757d;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button Styling */
|
|
||||||
.btn-danger {
|
|
||||||
background-color: var(--kaauh-danger);
|
|
||||||
border-color: var(--kaauh-danger);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.btn-danger:hover {
|
|
||||||
background-color: #c82333;
|
|
||||||
border-color: #bd2130;
|
|
||||||
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background-color: #6c757d;
|
|
||||||
border-color: #6c757d;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Consequence List */
|
|
||||||
.consequence-list {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consequence-list li {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
border-bottom: 1px solid #e9ecef;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consequence-list li:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consequence-list li i {
|
|
||||||
color: var(--kaauh-danger);
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Candidate Info Style */
|
|
||||||
.candidate-avatar {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 3px solid var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-placeholder {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #e9ecef;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 3px solid var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Heroicon adjustments to match font-awesome size */
|
|
||||||
.heroicon {
|
|
||||||
width: 1.2em;
|
|
||||||
height: 1.2em;
|
|
||||||
vertical-align: -0.125em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<!-- Header -->
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 class="text-3xl font-bold text-temple-red mb-2 flex items-center gap-3">
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="alert-triangle" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
{% trans "Delete Application" %}
|
{% trans "Delete Application" %}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">
|
<p class="text-gray-600">
|
||||||
{% trans "You are about to delete an Application record. This action cannot be undone." %}
|
{% trans "You are about to delete an Application record. This action cannot be undone." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{# Assuming application_detail URL takes object.pk or object.slug #}
|
<a href="{% url 'application_detail' object.pk %}" class="inline-flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-6 py-2.5 rounded-xl transition">
|
||||||
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary">
|
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Application" %}
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Application" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="flex justify-center">
|
||||||
<div class="col-lg-8">
|
<div class="w-full max-w-4xl">
|
||||||
<div class="warning-section">
|
<!-- Warning Section -->
|
||||||
<div class="warning-icon">
|
<div class="bg-gradient-to-br from-yellow-100 to-amber-50 border border-yellow-200 rounded-2xl p-8 mb-6 text-center">
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
<div class="bg-temple-red/10 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i data-lucide="alert-triangle" class="w-12 h-12 text-temple-red"></i>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3>
|
<h3 class="text-2xl font-bold text-amber-800 mb-3">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||||
<p class="warning-text">
|
<p class="text-amber-700">
|
||||||
{% blocktrans with candidate_name=object.candidate.full_name job_title=object.job.title %}
|
{% blocktrans with candidate_name=object.candidate.full_name job_title=object.job.title %}
|
||||||
Deleting the application submitted by **{{ candidate_name }}** for the job **{{ job_title }}** will permanently remove all associated data. Please review the information below carefully before proceeding.
|
Deleting application submitted by <strong>{{ candidate_name }}</strong> for job <strong>{{ job_title }}</strong> will permanently remove all associated data. Please review information below carefully before proceeding.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card kaauh-card mb-4">
|
<!-- Application Info Card -->
|
||||||
<div class="card-header bg-white border-bottom">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
<div class="bg-white border-b border-gray-200 p-5">
|
||||||
<i class="fas fa-file-alt me-2"></i>
|
<h5 class="text-lg font-bold text-temple-red flex items-center gap-2">
|
||||||
|
<i data-lucide="file-text" class="w-5 h-5"></i>
|
||||||
{% trans "Application to be Deleted" %}
|
{% trans "Application to be Deleted" %}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<div class="app-info">
|
<div class="bg-gray-50 rounded-xl p-6 mb-6">
|
||||||
{% if object.candidate %}
|
{% if object.candidate %}
|
||||||
<div class="d-flex align-items-center mb-4">
|
<div class="flex items-center gap-4 mb-5 pb-5 border-b border-gray-200">
|
||||||
{# Assuming candidate has a profile_image field #}
|
|
||||||
{% if object.candidate.profile_image %}
|
{% if object.candidate.profile_image %}
|
||||||
<img src="{{ object.candidate.profile_image.url }}" alt="{{ object.candidate.full_name }}" class="candidate-avatar me-3">
|
<img src="{{ object.candidate.profile_image.url }}" alt="{{ object.candidate.full_name }}" class="w-20 h-20 rounded-full object-cover border-3 border-temple-red">
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="avatar-placeholder me-3">
|
<div class="w-20 h-20 rounded-full bg-gray-200 flex items-center justify-center border-3 border-temple-red">
|
||||||
<i class="fas fa-user text-muted fa-2x"></i>
|
<i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<h4 class="mb-1">{{ object.candidate.full_name }}</h4>
|
<h4 class="text-xl font-bold text-gray-900 mb-1">{{ object.candidate.full_name }}</h4>
|
||||||
{% if object.candidate.email %}
|
{% if object.candidate.email %}
|
||||||
<p class="text-muted mb-0">{{ object.candidate.email }}</p>
|
<p class="text-gray-600">{{ object.candidate.email }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if object.job %}
|
{% if object.job %}
|
||||||
<div class="info-item">
|
<div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
|
||||||
<div class="info-icon">
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||||
<i class="fas fa-briefcase"></i>
|
<i data-lucide="briefcase" class="w-5 h-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-content">
|
<div class="flex-1">
|
||||||
<div class="info-label">{% trans "Job Title" %}</div>
|
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Job Title" %}</div>
|
||||||
<div class="info-value">{{ object.job.title }}</div>
|
<div class="text-gray-900 text-base">{{ object.job.title }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="info-item">
|
<div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
|
||||||
<div class="info-icon">
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||||
<i class="fas fa-calendar-alt"></i>
|
<i data-lucide="calendar" class="w-5 h-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-content">
|
<div class="flex-1">
|
||||||
<div class="info-label">{% trans "Applied On" %}</div>
|
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Applied On" %}</div>
|
||||||
<div class="info-value">{{ object.created_at|date:"F d, Y \a\t P" }}</div>
|
<div class="text-gray-900 text-base">{{ object.created_at|date:"F d, Y \a\t P" }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if object.status %}
|
{% if object.status %}
|
||||||
<div class="info-item">
|
<div class="flex items-start gap-4">
|
||||||
<div class="info-icon">
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||||
<i class="fas fa-cogs"></i>
|
<i data-lucide="settings" class="w-5 h-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-content">
|
<div class="flex-1">
|
||||||
<div class="info-label">{% trans "Current Status" %}</div>
|
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Current Status" %}</div>
|
||||||
<div class="info-value">{{ object.get_status_display }}</div>
|
<div class="text-gray-900 text-base">{{ object.get_status_display }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -273,63 +103,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card kaauh-card mb-4">
|
<!-- Consequences Card -->
|
||||||
<div class="card-header bg-white border-bottom">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
<div class="bg-white border-b border-gray-200 p-5">
|
||||||
<i class="fas fa-list me-2"></i>
|
<h5 class="text-lg font-bold text-temple-red flex items-center gap-2">
|
||||||
|
<i data-lucide="list" class="w-5 h-5"></i>
|
||||||
{% trans "What will happen when you delete this Application?" %}
|
{% trans "What will happen when you delete this Application?" %}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<ul class="consequence-list">
|
<ul class="space-y-3">
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "The Application record and all status history will be permanently deleted." %}
|
{% trans "The Application record and all status history will be permanently deleted." %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "All associated screening results, scores, and evaluations will be removed." %}
|
{% trans "All associated screening results, scores, and evaluations will be removed." %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "Any linked documents (CV, cover letter) specific to this application will be lost." %}
|
{% trans "Any linked documents (CV, cover letter) specific to this application will be lost." %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "The Candidate will still exist, but this specific application history will be lost." %}
|
{% trans "The Candidate will still exist, but this specific application history will be lost." %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "This action cannot be undone under any circumstances." %}
|
{% trans "This action cannot be undone under any circumstances." %}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card kaauh-card">
|
<!-- Confirmation Form -->
|
||||||
<div class="card-body">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
<div class="p-6">
|
||||||
<form method="post" id="deleteForm">
|
<form method="post" id="deleteForm">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-6">
|
||||||
<div class="form-check">
|
<label class="flex items-start gap-3 cursor-pointer">
|
||||||
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required>
|
<input type="checkbox" id="confirm_delete" name="confirm_delete" required class="w-5 h-5 mt-0.5 rounded border-gray-300 text-temple-red focus:ring-temple-red focus:ring-offset-0">
|
||||||
<label class="form-check-label" for="confirm_delete">
|
<span class="text-gray-900 font-medium">
|
||||||
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this application." %}</strong>
|
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this application." %}</strong>
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="flex flex-col sm:flex-row gap-3 justify-between">
|
||||||
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary btn-lg">
|
<a href="{% url 'application_detail' object.pk %}" class="inline-flex items-center justify-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-8 py-3 rounded-xl transition">
|
||||||
<i class="fas fa-times me-2"></i>
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
<button type="submit"
|
<button type="submit" id="deleteButton" disabled class="inline-flex items-center justify-center gap-2 bg-red-500 hover:bg-red-600 text-white font-semibold px-8 py-3 rounded-xl transition shadow-sm hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
class="btn btn-danger btn-lg"
|
<i data-lucide="trash-2" class="w-5 h-5"></i>
|
||||||
id="deleteButton"
|
|
||||||
disabled>
|
|
||||||
<i class="fas fa-trash me-2"></i>
|
|
||||||
{% trans "Delete Application Permanently" %}
|
{% trans "Delete Application Permanently" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -349,47 +178,33 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
function validateForm() {
|
function validateForm() {
|
||||||
const checkboxChecked = confirmDeleteCheckbox.checked;
|
const checkboxChecked = confirmDeleteCheckbox.checked;
|
||||||
deleteButton.disabled = !checkboxChecked;
|
deleteButton.disabled = !checkboxChecked;
|
||||||
|
|
||||||
// Toggle button classes for visual feedback
|
|
||||||
if (checkboxChecked) {
|
|
||||||
deleteButton.classList.remove('btn-secondary');
|
|
||||||
deleteButton.classList.add('btn-danger');
|
|
||||||
} else {
|
|
||||||
deleteButton.classList.remove('btn-danger');
|
|
||||||
deleteButton.classList.add('btn-secondary');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmDeleteCheckbox.addEventListener('change', validateForm);
|
confirmDeleteCheckbox.addEventListener('change', validateForm);
|
||||||
// Initialize state on page load
|
|
||||||
validateForm();
|
validateForm();
|
||||||
|
|
||||||
// Add confirmation and prevent double submission before final submission
|
|
||||||
deleteForm.addEventListener('submit', function(event) {
|
deleteForm.addEventListener('submit', function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const candidateName = "{{ object.candidate.full_name|escapejs }}";
|
const candidateName = "{{ object.candidate.full_name|escapejs }}";
|
||||||
const jobTitle = "{{ object.job.title|escapejs }}";
|
const jobTitle = "{{ object.job.title|escapejs }}";
|
||||||
|
|
||||||
// Construct a confirmation message using string replacement for safety
|
const confirmationMessageTemplate = "{% blocktrans with candidate_name='CANDIDATE_PLACEHOLDER' job_title='JOB_PLACEHOLDER' %}Are you absolutely sure you want to permanently delete application by CANDIDATE_PLACEHOLDER for JOB_PLACEHOLDER? This action cannot be reversed!{% endblocktrans %}";
|
||||||
const confirmationMessageTemplate = "{% blocktrans with candidate_name='CANDIDATE_PLACEHOLDER' job_title='JOB_PLACEHOLDER' %}Are you absolutely sure you want to permanently delete the application by CANDIDATE_PLACEHOLDER for JOB_PLACEHOLDER? This action cannot be reversed!{% endblocktrans %}";
|
|
||||||
|
|
||||||
const confirmationMessage = confirmationMessageTemplate
|
const confirmationMessage = confirmationMessageTemplate
|
||||||
.replace('CANDIDATE_PLACEHOLDER', candidateName)
|
.replace('CANDIDATE_PLACEHOLDER', candidateName)
|
||||||
.replace('JOB_PLACEHOLDER', jobTitle);
|
.replace('JOB_PLACEHOLDER', jobTitle);
|
||||||
|
|
||||||
if (confirm(confirmationMessage)) {
|
if (confirm(confirmationMessage)) {
|
||||||
// Disable button and show loading state
|
|
||||||
deleteButton.disabled = true;
|
deleteButton.disabled = true;
|
||||||
deleteButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>{% trans "Deleting..." %}';
|
deleteButton.innerHTML = '<svg class="animate-spin w-5 h-5 mr-2" viewBox="0 0 50 50"><circle cx="25" cy="25" r="20" fill="none" stroke="currentColor" stroke-width="5" class="opacity-25"></circle><path fill="none" stroke="currentColor" stroke-width="5" d="M25 5a20 20 0 1 1 0 0 40 20 20 0 1 1 0 0-40" class="opacity-75"></path></svg>{% trans "Deleting..." %}';
|
||||||
|
|
||||||
// Submit the form programmatically
|
|
||||||
deleteForm.submit();
|
deleteForm.submit();
|
||||||
} else {
|
} else {
|
||||||
// If the user cancels the dialog, ensure the button state is reset/validated
|
|
||||||
validateForm();
|
validateForm();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lucide.createIcons();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -3,211 +3,99 @@
|
|||||||
|
|
||||||
{% block title %}{{ application.name }} - {{ block.super }}{% endblock %}
|
{% block title %}{{ application.name }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* Card Hover Effects */
|
|
||||||
.detail-card {
|
|
||||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tab Button Effects */
|
|
||||||
.tab-btn {
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-btn:hover {
|
|
||||||
background-color: rgba(157, 34, 53, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-btn.active {
|
|
||||||
border-bottom-color: #9d2235;
|
|
||||||
color: #9d2235;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button Hover Effects */
|
|
||||||
.btn-action {
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-action:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 12px rgba(157, 34, 53, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Timeline Animation */
|
|
||||||
.timeline-item {
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-item:hover {
|
|
||||||
transform: translateX(4px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Info Card Animation */
|
|
||||||
.info-card {
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Progress Bar Animation */
|
|
||||||
.progress-bar {
|
|
||||||
transition: width 0.5s ease;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
<div class="px-4 py-6" id="application-detail-content">
|
||||||
|
<nav aria-label="breadcrumb" class="mb-6">
|
||||||
{# Breadcrumb #}
|
<ol class="flex items-center space-x-2 text-sm">
|
||||||
<nav class="mb-6" aria-label="breadcrumb">
|
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition">Home</a></li>
|
||||||
<ol class="flex items-center gap-2 text-sm">
|
|
||||||
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
|
|
||||||
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Home" %}
|
|
||||||
</a></li>
|
|
||||||
<li class="text-gray-400">/</li>
|
<li class="text-gray-400">/</li>
|
||||||
<li><a href="{% url 'job_detail' application.job.slug %}" class="text-gray-500 hover:text-temple-red transition">
|
<li><a href="{% url 'job_detail' application.job.slug %}" class="text-gray-500 hover:text-temple-red transition">Job: {{application.job.title}}</a></li>
|
||||||
{% trans "Job:" %} ({{ application.job.title }})
|
|
||||||
</a></li>
|
|
||||||
<li class="text-gray-400">/</li>
|
<li class="text-gray-400">/</li>
|
||||||
<li class="text-temple-red font-semibold" aria-current="page">{% trans "Application Detail" %}</li>
|
<li class="font-semibold text-temple-red" aria-current="page">{% trans "Applicant Detail" %}</li>
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||||
|
{# LEFT COLUMN: MAIN application DETAILS AND TABS #}
|
||||||
{# LEFT COLUMN: MAIN DETAILS AND TABS #}
|
<div class="lg:col-span-2">
|
||||||
<div class="lg:col-span-8">
|
<div class="bg-white rounded-xl shadow-md overflow-hidden border border-gray-200">
|
||||||
<div class="detail-card bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
||||||
|
|
||||||
{# HEADER SECTION #}
|
{# HEADER SECTION #}
|
||||||
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-6">
|
<div class="bg-gradient-to-br from-temple-red to-red-800 text-white p-6">
|
||||||
<div class="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||||
<div class="flex-1">
|
<div>
|
||||||
<h1 class="text-2xl sm:text-3xl font-bold mb-2">{{ application.name }}</h1>
|
<h1 class="text-3xl font-extrabold mb-2">{{ application.name }}</h1>
|
||||||
<div class="flex items-center gap-2 mb-2">
|
<div class="flex items-center gap-2 mb-2">
|
||||||
<span class="inline-block text-xs font-bold uppercase tracking-wide px-3 py-1.5 rounded-full bg-white/20 backdrop-blur-sm">
|
<span id="stageDisplay" class="bg-white/20 backdrop-blur-sm px-3 py-1 rounded-full text-sm font-medium">
|
||||||
{% trans "Stage:" %} <span class="font-normal">{{ application.stage }}</span>
|
{% trans "Stage:" %}
|
||||||
|
<span class="font-bold">{{ application.stage }}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-white/80">
|
<p class="text-red-100 text-sm">
|
||||||
{% trans "Applied for:" %} <strong class="text-white">{{ application.job.title }}</strong>
|
{% trans "Applied for:" %} <strong>{{ application.job.title }}</strong>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Change Stage button #}
|
{# Change Stage button #}
|
||||||
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
|
{% if user.is_staff and user == application.job.assigned_to or user.is_superuser %}
|
||||||
<button type="button"
|
<button type="button" class="stage-modal-trigger bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2" data-modal="stageUpdateModal">
|
||||||
class="btn-primary inline-flex items-center gap-2 bg-white/10 hover:bg-white/20 text-white font-medium px-4 py-2 rounded-xl text-sm transition"
|
<i data-lucide="repeat" class="w-4 h-4"></i> {% trans "Change Stage" %}
|
||||||
onclick="document.getElementById('stageUpdateModal').classList.remove('hidden')">
|
|
||||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
|
||||||
{% trans "Change Stage" %}
|
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# TABS NAVIGATION #}
|
{# LEFT TABS NAVIGATION #}
|
||||||
<div class="border-b border-gray-200 bg-gray-50 overflow-x-auto">
|
<div class="bg-gray-50 border-b border-gray-200 px-6">
|
||||||
<div class="flex min-w-max px-4 gap-1">
|
<ul class="flex space-x-1" id="candidateTabs" role="tablist">
|
||||||
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
|
<li class="role-presentation">
|
||||||
onclick="showTab('contact-pane')"
|
<button class="tab-btn px-4 py-3 text-sm font-medium border-b-2 border-transparent hover:text-temple-red transition flex items-center gap-2 active text-temple-dark border-b-temple-red bg-white" id="contact-tab" data-tab="contact-pane" type="button" role="tab" aria-controls="contact-pane" aria-selected="true">
|
||||||
data-tab="contact-pane">
|
<i data-lucide="id-card" class="w-4 h-4"></i> {% trans "Contact & Job" %}
|
||||||
<i data-lucide="id-card" class="w-4 h-4 mr-2 inline"></i>
|
|
||||||
{% trans "Contact & Job" %}
|
|
||||||
</button>
|
</button>
|
||||||
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
|
</li>
|
||||||
onclick="showTab('timeline-pane')"
|
|
||||||
data-tab="timeline-pane">
|
<li class="role-presentation">
|
||||||
<i data-lucide="route" class="w-4 h-4 mr-2 inline"></i>
|
<button class="tab-btn px-4 py-3 text-sm font-medium text-gray-600 border-b-2 border-transparent hover:text-temple-red transition flex items-center gap-2" id="timeline-tab" data-tab="timeline-pane" type="button" role="tab" aria-controls="timeline-pane" aria-selected="false">
|
||||||
{% trans "Journey Timeline" %}
|
<i data-lucide="route" class="w-4 h-4"></i> {% trans "Journey Timeline" %}
|
||||||
</button>
|
</button>
|
||||||
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
|
</li>
|
||||||
onclick="showTab('documents-pane')"
|
|
||||||
data-tab="documents-pane">
|
<li class="role-presentation">
|
||||||
<i data-lucide="file-text" class="w-4 h-4 mr-2 inline"></i>
|
<button class="tab-btn px-4 py-3 text-sm font-medium text-gray-600 border-b-2 border-transparent hover:text-temple-red transition flex items-center gap-2" id="documents-tab" data-tab="documents-pane" type="button" role="tab" aria-controls="documents-pane" aria-selected="false">
|
||||||
{% trans "Documents" %}
|
<i data-lucide="file-text" class="w-4 h-4"></i> {% trans "Documents" %}
|
||||||
</button>
|
</button>
|
||||||
{% if application.parsed_summary %}
|
</li>
|
||||||
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
|
</ul>
|
||||||
onclick="showTab('summary-pane')"
|
|
||||||
data-tab="summary-pane">
|
|
||||||
<i data-lucide="sparkles" class="w-4 h-4 mr-2 inline"></i>
|
|
||||||
{% trans "AI Summary" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if application.is_resume_parsed %}
|
|
||||||
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
|
|
||||||
onclick="showTab('analysis-pane')"
|
|
||||||
data-tab="analysis-pane">
|
|
||||||
<i data-lucide="bar-chart-3" class="w-4 h-4 mr-2 inline"></i>
|
|
||||||
{% trans "AI Analysis" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
|
<div class="tab-content" id="candidateTabsContent">
|
||||||
|
|
||||||
{# TAB 1: CONTACT & DATES #}
|
{# TAB 1 CONTENT: CONTACT & DATES #}
|
||||||
<div id="contact-pane" class="tab-content">
|
<div class="tab-pane block" id="contact-pane" role="tabpanel" aria-labelledby="contact-tab">
|
||||||
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
|
<h5 class="text-lg font-bold text-temple-red mb-4">{% trans "Core Details" %}</h5>
|
||||||
<i data-lucide="info" class="w-5 h-5"></i>
|
|
||||||
{% trans "Core Details" %}
|
|
||||||
</h5>
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200">
|
<div class="flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
|
||||||
<div class="flex items-center gap-3">
|
<i data-lucide="mail" class="w-8 h-8 text-gray-400 shrink-0"></i>
|
||||||
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
||||||
<i data-lucide="mail" class="w-6 h-6 text-temple-red"></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Email" %}</p>
|
<p class="text-xs text-gray-500">{% trans "Email" %}</p>
|
||||||
<p class="font-semibold text-gray-900">{{ application.email }}</p>
|
<p class="font-semibold text-gray-800">{{ application.email }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
|
||||||
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200">
|
<i data-lucide="briefcase" class="w-8 h-8 text-gray-400 shrink-0"></i>
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
||||||
<i data-lucide="briefcase" class="w-6 h-6 text-temple-red"></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Position Applied" %}</p>
|
<p class="text-xs text-gray-500">{% trans "Position Applied" %}</p>
|
||||||
<p class="font-semibold text-gray-900">{{ application.job.title }}</p>
|
<p class="font-semibold text-gray-800">{{ application.job.title }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="md:col-span-2 flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
|
||||||
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200 md:col-span-2">
|
<i data-lucide="calendar-check" class="w-8 h-8 text-gray-400 shrink-0"></i>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-xs text-gray-500">{% trans "Applied Date" %}</p>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
<p class="font-semibold text-gray-800">{{ application.created_at|date:"M d, Y H:i" }}</p>
|
||||||
<i data-lucide="calendar-check" class="w-6 h-6 text-temple-red"></i>
|
<span class="bg-gray-200 text-gray-700 px-2 py-1 rounded text-xs font-medium">
|
||||||
</div>
|
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i>
|
||||||
<div>
|
|
||||||
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Applied Date" %}</p>
|
|
||||||
<div class="flex items-center gap-2 flex-wrap">
|
|
||||||
<p class="font-semibold text-gray-900">{{ application.created_at|date:"M d, Y H:i" }}</p>
|
|
||||||
<span class="inline-flex items-center gap-1 text-xs font-medium px-2 py-1 rounded-full bg-temple-red/10 text-temple-red">
|
|
||||||
<i data-lucide="clock" class="w-3 h-3"></i>
|
|
||||||
{{ application.created_at|naturaltime }}
|
{{ application.created_at|naturaltime }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -215,108 +103,97 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{# TAB 2: JOURNEY TIMELINE #}
|
{# TAB 2 CONTENT: TIMELINE #}
|
||||||
<div id="timeline-pane" class="tab-content hidden">
|
<div class="tab-pane hidden" id="timeline-pane" role="tabpanel" aria-labelledby="timeline-tab">
|
||||||
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
<div class="bg-white border border-gray-200 rounded-xl">
|
||||||
<div class="p-4 border-b border-gray-200">
|
<div class="p-4 border-b border-gray-200">
|
||||||
<h5 class="text-sm font-bold text-gray-600 flex items-center gap-2">
|
<h5 class="text-sm font-semibold text-gray-600 flex items-center gap-2">
|
||||||
<i data-lucide="route" class="w-5 h-5"></i>
|
<i data-lucide="route" class="w-4 h-4"></i>{% trans "Application Journey" %}
|
||||||
{% trans "Application Journey" %}
|
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6">
|
<div class="p-6">
|
||||||
|
<h6 class="text-xs uppercase tracking-wider text-gray-500 font-bold mb-3">{% trans "Current Stage" %}</h6>
|
||||||
<p class="text-xs font-bold text-gray-500 uppercase tracking-wide mb-4">{% trans "Current Stage" %}</p>
|
<div class="p-4 mb-4 rounded-lg bg-red-50 border border-red-200">
|
||||||
<div class="bg-temple-red/5 border border-temple-red/30 rounded-xl p-4 mb-6">
|
<p class="font-bold text-lg text-temple-dark mb-1">{{ application.stage }}</p>
|
||||||
<p class="text-xl font-bold text-temple-red mb-1">{{ application.stage }}</p>
|
|
||||||
<p class="text-xs text-gray-500">
|
<p class="text-xs text-gray-500">
|
||||||
{% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }}
|
{% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-xs font-bold text-gray-500 uppercase tracking-wide mb-4 pt-4 border-t border-gray-200">{% trans "Historical Timeline" %}</p>
|
<h6 class="text-xs uppercase tracking-wider text-gray-500 font-bold mb-3 pt-4 border-t border-gray-200">{% trans "Historical Timeline" %}</h6>
|
||||||
<div class="relative pl-8 border-l-2 border-gray-200 space-y-6">
|
<div class="relative pl-8">
|
||||||
|
<div class="absolute left-3.5 top-0 bottom-0 w-0.5 bg-gray-200"></div>
|
||||||
|
|
||||||
{# Application Submitted #}
|
<div class="relative mb-6">
|
||||||
<div class="timeline-item relative">
|
<div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
|
||||||
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-temple-red flex items-center justify-center text-white border-4 border-white shadow-lg">
|
<i data-lucide="file-signature" class="w-4 h-4"></i>
|
||||||
<i data-lucide="file-signature" class="w-3 h-3"></i>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-12">
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Application Submitted" %}</p>
|
<p class="font-semibold text-gray-800">{% trans "Application Submitted" %}</p>
|
||||||
<p class="text-xs text-gray-500">
|
<p class="text-xs text-gray-500">
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.created_at|date:"M d, Y" }}
|
||||||
{{ application.created_at|date:"M d, Y" }}
|
|
||||||
<span class="mx-2">|</span>
|
<span class="mx-2">|</span>
|
||||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.created_at|date:"h:i A" }}
|
||||||
{{ application.created_at|date:"h:i A" }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if application.exam_date %}
|
{% if application.exam_date %}
|
||||||
<div class="timeline-item relative">
|
<div class="relative mb-6">
|
||||||
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-temple-red flex items-center justify-center text-white border-4 border-white shadow-lg">
|
<div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
|
||||||
<i data-lucide="clipboard-check" class="w-3 h-3"></i>
|
<i data-lucide="clipboard-check" class="w-4 h-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-12">
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Exam" %}</p>
|
<p class="font-semibold text-gray-800">{% trans "Exam" %}</p>
|
||||||
<p class="text-xs text-gray-500">
|
<p class="text-xs text-gray-500">
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.exam_date|date:"M d, Y" }}
|
||||||
{{ application.exam_date|date:"M d, Y" }}
|
|
||||||
<span class="mx-2">|</span>
|
<span class="mx-2">|</span>
|
||||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.exam_date|date:"h:i A" }}
|
||||||
{{ application.exam_date|date:"h:i A" }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if application.get_interview_date %}
|
{% if application.get_interview_date %}
|
||||||
<div class="timeline-item relative">
|
<div class="relative mb-6">
|
||||||
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-amber-500 flex items-center justify-center text-white border-4 border-white shadow-lg">
|
<div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
|
||||||
<i data-lucide="message-circle" class="w-3 h-3"></i>
|
<i data-lucide="message-circle" class="w-4 h-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-12">
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Interview" %}</p>
|
<p class="font-semibold text-gray-800">{% trans "Interview" %}</p>
|
||||||
<p class="text-xs text-gray-500">
|
<p class="text-xs text-gray-500">
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.get_interview_date}}
|
||||||
{{ application.get_interview_date }}
|
|
||||||
<span class="mx-2">|</span>
|
<span class="mx-2">|</span>
|
||||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.get_interview_time}}
|
||||||
{{ application.get_interview_time }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if application.offer_date %}
|
{% if application.offer_date %}
|
||||||
<div class="timeline-item relative">
|
<div class="relative mb-6">
|
||||||
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-emerald-500 flex items-center justify-center text-white border-4 border-white shadow-lg">
|
<div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
|
||||||
<i data-lucide="handshake" class="w-3 h-3"></i>
|
<i data-lucide="handshake" class="w-4 h-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-12">
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Offer" %}</p>
|
<p class="font-semibold text-gray-800">{% trans "Offer" %}</p>
|
||||||
<p class="text-xs text-gray-500">
|
<p class="text-xs text-gray-500">
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.offer_date|date:"M d, Y" }}
|
||||||
{{ application.offer_date|date:"M d, Y" }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if application.hired_date %}
|
{% if application.hired_date %}
|
||||||
<div class="timeline-item relative">
|
<div class="relative mb-6">
|
||||||
<div class="absolute -left-[2.5rem] top-0 w-8 h-8 rounded-full bg-emerald-600 flex items-center justify-center text-white border-4 border-white shadow-lg">
|
<div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
|
||||||
<i data-lucide="user-check" class="w-3 h-3"></i>
|
<i data-lucide="check-circle" class="w-4 h-4"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
<div class="ml-12">
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Hired" %}</p>
|
<p class="font-semibold text-gray-800">{% trans "Hired" %}</p>
|
||||||
<p class="text-xs text-gray-500">
|
<p class="text-xs text-gray-500">
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.hired_date|date:"M d, Y" }}
|
||||||
{{ application.hired_date|date:"M d, Y" }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -326,158 +203,31 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# TAB 3: DOCUMENTS #}
|
{# TAB 3 CONTENT: DOCUMENTS #}
|
||||||
<div id="documents-pane" class="tab-content hidden">
|
<div class="tab-pane hidden" id="documents-pane" role="tabpanel" aria-labelledby="documents-tab">
|
||||||
{% with documents=application.documents %}
|
{% with documents=application.documents %}
|
||||||
{% include 'includes/document_list.html' %}
|
{% include 'includes/document_list.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{# TAB 4: AI SUMMARY #}
|
{# RIGHT COLUMN: ACTIONS AND TIMING #}
|
||||||
{% if application.parsed_summary %}
|
<div class="space-y-6">
|
||||||
<div id="summary-pane" class="tab-content hidden">
|
{# ACTIONS CARD #}
|
||||||
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
|
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-200">
|
||||||
<i data-lucide="sparkles" class="w-5 h-5"></i>
|
<h5 class="text-sm font-semibold text-gray-600 mb-4 flex items-center gap-2">
|
||||||
{% trans "AI Generated Summary" %}
|
<i data-lucide="settings" class="w-4 h-4"></i>{% trans "Management Actions" %}
|
||||||
</h5>
|
</h5>
|
||||||
<div class="bg-gray-50 rounded-xl p-4 border-l-4 border-temple-red">
|
<div class="space-y-3">
|
||||||
{% include 'includes/application_modal_body.html' %}
|
<a href="{% url 'application_list' %}" class="w-full flex items-center justify-center gap-2 bg-gray-100 hover:bg-gray-200 text-gray-700 py-3 px-4 rounded-lg text-sm font-medium transition">
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# TAB 5: AI ANALYSIS #}
|
|
||||||
{% if application.is_resume_parsed %}
|
|
||||||
<div id="analysis-pane" class="tab-content hidden">
|
|
||||||
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
|
|
||||||
<i data-lucide="bar-chart-3" class="w-5 h-5"></i>
|
|
||||||
{% trans "AI Analysis Report" %}
|
|
||||||
</h5>
|
|
||||||
<div class="bg-gray-50 rounded-xl p-4 border-l-4 border-temple-red">
|
|
||||||
{% with analysis=application.ai_analysis_data %}
|
|
||||||
|
|
||||||
{# Match Score Card #}
|
|
||||||
<div class="bg-white rounded-xl p-4 mb-4 shadow-sm">
|
|
||||||
<div class="flex justify-between items-center mb-3">
|
|
||||||
<h6 class="font-bold text-gray-900">{% trans "Match Score" %}</h6>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-bold {% if analysis.match_score >= 70 %}bg-emerald-500 text-white{% elif analysis.match_score >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
|
|
||||||
{{ analysis.match_score }}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="h-2 bg-gray-200 rounded-full overflow-hidden">
|
|
||||||
<div class="progress-bar h-full {% if analysis.match_score >= 70 %}bg-emerald-500{% elif analysis.match_score >= 40 %}bg-amber-500{% else %}bg-red-500{% endif %}"
|
|
||||||
style="width: {{ analysis.match_score }}%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Category & Job Fit #}
|
|
||||||
<div class="mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Category" %}</h6>
|
|
||||||
<p class="text-gray-700 mb-3">{{ analysis.category }}</p>
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Job Fit Narrative" %}</h6>
|
|
||||||
<p class="text-gray-700">{{ analysis.job_fit_narrative }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Strengths and Weaknesses #}
|
|
||||||
<div class="mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Strengths" %}</h6>
|
|
||||||
<p class="text-emerald-600 mb-3">{{ analysis.strengths }}</p>
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Weaknesses" %}</h6>
|
|
||||||
<p class="text-red-600">{{ analysis.weaknesses }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Recommendation #}
|
|
||||||
<div class="bg-temple-red/5 rounded-xl p-4 mb-4 border border-temple-red/20">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Recommendation" %}</h6>
|
|
||||||
<p class="text-gray-700">{{ analysis.recommendation }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Top Keywords #}
|
|
||||||
<div class="mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Top Keywords" %}</h6>
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
{% for keyword in analysis.top_3_keywords %}
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-temple-red/10 text-temple-red">
|
|
||||||
{{ keyword }}
|
|
||||||
</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Professional Details #}
|
|
||||||
<div class="bg-white rounded-xl p-4 shadow-sm mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-3">{% trans "Professional Details" %}</h6>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<p class="flex justify-between"><span class="text-gray-600">{% trans "Years of Experience:" %}</span> <strong>{{ analysis.years_of_experience }}</strong></p>
|
|
||||||
<p class="flex justify-between"><span class="text-gray-600">{% trans "Most Recent Job Title:" %}</span> <strong>{{ analysis.most_recent_job_title }}</strong></p>
|
|
||||||
<p class="flex justify-between">
|
|
||||||
<span class="text-gray-600">{% trans "Experience Industry Match:" %}</span>
|
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-bold {% if analysis.experience_industry_match >= 70 %}bg-emerald-500 text-white{% elif analysis.experience_industry_match >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
|
|
||||||
{{ analysis.experience_industry_match }}%
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p class="flex justify-between"><span class="text-gray-600">{% trans "Soft Skills Score:" %}</span> <strong>{{ analysis.soft_skills_score }}%</strong></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Criteria Checklist #}
|
|
||||||
<div class="mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Criteria Assessment" %}</h6>
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="w-full text-sm">
|
|
||||||
<thead>
|
|
||||||
<tr class="border-b border-gray-200">
|
|
||||||
<th class="text-left py-2 px-3 font-semibold text-gray-700">{% trans "Criteria" %}</th>
|
|
||||||
<th class="text-left py-2 px-3 font-semibold text-gray-700">{% trans "Status" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for criterion, status in analysis.criteria_checklist.items %}
|
|
||||||
<tr class="border-b border-gray-100">
|
|
||||||
<td class="py-2 px-3 text-gray-700">{{ criterion }}</td>
|
|
||||||
<td class="py-2 px-3">
|
|
||||||
{% if status == "Met" %}
|
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-700">{% trans "Met" %}</span>
|
|
||||||
{% elif status == "Not Met" %}
|
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-red-100 text-red-700">{% trans "Not Met" %}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-gray-100 text-gray-700">{{ status }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# RIGHT COLUMN: ACTIONS #}
|
|
||||||
<div class="lg:col-span-4 space-y-4">
|
|
||||||
|
|
||||||
{# Management Actions #}
|
|
||||||
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
|
||||||
<h5 class="text-sm font-bold text-gray-600 mb-4 flex items-center gap-2">
|
|
||||||
<i data-lucide="settings" class="w-5 h-5"></i>
|
|
||||||
{% trans "Management Actions" %}
|
|
||||||
</h5>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<a href="{% url 'application_list' %}"
|
|
||||||
class="btn-action inline-flex items-center gap-2 w-full bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium px-4 py-2.5 rounded-xl text-sm transition">
|
|
||||||
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
{% trans "Back to List" %}
|
{% trans "Back to List" %}
|
||||||
</a>
|
</a>
|
||||||
{% if application.resume %}
|
{% if application.resume %}
|
||||||
<a href="{{ application.resume.url }}" download
|
<a href="{{ application.resume.url }}" download class="w-full flex items-center justify-center gap-2 bg-temple-red hover:bg-red-800 text-white py-3 px-4 rounded-lg text-sm font-medium transition shadow-sm">
|
||||||
class="btn-primary inline-flex items-center gap-2 w-full bg-temple-red hover:bg-[#8a1e2f] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-lg hover:shadow-xl">
|
|
||||||
<i data-lucide="download" class="w-4 h-4"></i>
|
<i data-lucide="download" class="w-4 h-4"></i>
|
||||||
{% trans "Download Resume" %}
|
{% trans "Download Resume" %}
|
||||||
</a>
|
</a>
|
||||||
@ -485,102 +235,263 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Time to Hire #}
|
{# TIME TO HIRE CARD #}
|
||||||
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-200">
|
||||||
<h5 class="text-sm font-bold text-gray-600 mb-3 flex items-center gap-2">
|
<h5 class="text-sm font-semibold text-gray-600 mb-4 flex items-center gap-2">
|
||||||
<i data-lucide="clock" class="w-5 h-5"></i>
|
<i data-lucide="clock" class="w-4 h-4"></i>{% trans "Time to Hire:" %}
|
||||||
{% trans "Time to Hire:" %}
|
|
||||||
</h5>
|
</h5>
|
||||||
|
<div class="text-center">
|
||||||
{% with days=application.time_to_hire_days %}
|
{% with days=application.time_to_hire_days %}
|
||||||
<p class="text-2xl font-bold text-temple-red">
|
{% if days > 0 %}
|
||||||
{% if days > 0 %}{{ days }} day{{ days|pluralize }}{% else %}0{% endif %}
|
<p class="text-2xl font-bold text-temple-dark">{{ days }}</p>
|
||||||
</p>
|
<p class="text-sm text-gray-500">day{{ days|pluralize }}</p>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-2xl font-bold text-temple-dark">0</p>
|
||||||
|
<p class="text-sm text-gray-500">days</p>
|
||||||
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{# Resume Parsing Section #}
|
|
||||||
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
|
||||||
<h5 class="text-sm font-bold text-gray-600 mb-3 flex items-center gap-2">
|
|
||||||
<i data-lucide="file-text" class="w-5 h-5"></i>
|
|
||||||
{% trans "Resume Analysis" %}
|
|
||||||
</h5>
|
|
||||||
<div class="resume-parsed-section">
|
<div class="resume-parsed-section">
|
||||||
{% if application.is_resume_parsed %}
|
{% if application.is_resume_parsed %}
|
||||||
{% include 'recruitment/application_resume_template.html' %}
|
{% include 'recruitment/application_resume_template.html' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if application.scoring_timeout %}
|
{% if application.scoring_timeout %}
|
||||||
<div class="flex flex-col items-center justify-center py-6">
|
<div class="flex justify-center items-center py-12">
|
||||||
<div class="flex items-center gap-2 text-temple-red">
|
<div class="flex items-center gap-3 text-temple-red">
|
||||||
<i data-lucide="bot" class="w-6 h-6 animate-pulse"></i>
|
<i data-lucide="robot" class="w-8 h-8 animate-pulse"></i>
|
||||||
<span class="text-sm font-medium">{% trans "Resume Analysis In Progress..." %}</span>
|
<span class="text-sm">{% trans "Resume Analysis In Progress..." %}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="flex flex-col items-center justify-center py-6">
|
<div class="flex justify-center items-center py-12">
|
||||||
<button type="submit"
|
<button type="submit" class="flex items-center gap-2 bg-temple-red hover:bg-red-800 text-white py-3 px-6 rounded-lg text-sm font-medium transition shadow-sm" hx-get="{% url 'application_retry_scoring' application.slug %}" hx-select=".resume-parsed-section" hx-target=".resume-parsed-section" hx-swap="outerHTML" hx-on:click="this.disabled=true;this.innerHTML=`Scoring Resume, Please Wait... <i data-lucide='loader-2' class='w-4 h-4 animate-spin'></i>`">
|
||||||
class="btn-primary inline-flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-lg hover:shadow-xl"
|
|
||||||
hx-get="{% url 'application_retry_scoring' application.slug %}"
|
|
||||||
hx-select=".resume-parsed-section"
|
|
||||||
hx-target=".resume-parsed-section"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-on:click="this.disabled=true;this.innerHTML=`Scoring Resume, Please Wait...`">
|
|
||||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||||
{% trans "Unable to Parse Resume, Click to Retry" %}
|
{% trans "Unable to Parse Resume, click to retry" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Stage Update Modal for Staff Users #}
|
{# STAGE UPDATE MODAL INCLUDED FOR STAFF USERS #}
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<div id="stageUpdateModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
|
|
||||||
<div class="bg-white rounded-2xl shadow-xl max-w-lg w-full">
|
|
||||||
{% include "recruitment/partials/stage_update_modal.html" with application=application form=stage_form %}
|
{% include "recruitment/partials/stage_update_modal.html" with application=application form=stage_form %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize Lucide icons
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
|
|
||||||
// Tab switching functionality
|
// ========================================
|
||||||
function showTab(tabId) {
|
// Tab Navigation Functionality
|
||||||
// Hide all tab contents
|
// ========================================
|
||||||
document.querySelectorAll('.tab-content').forEach(content => {
|
const tabButtons = document.querySelectorAll('.tab-btn');
|
||||||
content.classList.add('hidden');
|
const tabPanes = document.querySelectorAll('.tab-pane');
|
||||||
|
|
||||||
|
tabButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
// Get the target tab pane ID
|
||||||
|
const targetTabId = this.getAttribute('data-tab');
|
||||||
|
|
||||||
|
// Remove active state from all buttons
|
||||||
|
tabButtons.forEach(btn => {
|
||||||
|
btn.classList.remove('active', 'text-temple-dark', 'border-b-temple-red', 'bg-white');
|
||||||
|
btn.classList.add('text-gray-600', 'border-transparent');
|
||||||
|
btn.setAttribute('aria-selected', 'false');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Remove active state from all tabs
|
// Add active state to clicked button
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
this.classList.add('active', 'text-temple-dark', 'border-b-temple-red', 'bg-white');
|
||||||
btn.classList.remove('active');
|
this.classList.remove('text-gray-600', 'border-transparent');
|
||||||
btn.classList.remove('border-temple-red', 'text-temple-red', 'bg-white');
|
this.setAttribute('aria-selected', 'true');
|
||||||
btn.classList.add('border-transparent', 'text-gray-600');
|
|
||||||
|
// Hide all tab panes
|
||||||
|
tabPanes.forEach(pane => {
|
||||||
|
pane.classList.add('hidden');
|
||||||
|
pane.classList.remove('block');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show selected tab content
|
// Show the target tab pane
|
||||||
document.getElementById(tabId).classList.remove('hidden');
|
const targetPane = document.getElementById(targetTabId);
|
||||||
|
if (targetPane) {
|
||||||
|
targetPane.classList.remove('hidden');
|
||||||
|
targetPane.classList.add('block');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Add active state to selected tab
|
// ========================================
|
||||||
const activeBtn = document.querySelector(`[data-tab="${tabId}"]`);
|
// Document Upload Modal Functionality
|
||||||
activeBtn.classList.add('active', 'border-temple-red', 'text-temple-red', 'bg-white');
|
// ========================================
|
||||||
activeBtn.classList.remove('border-transparent', 'text-gray-600');
|
|
||||||
|
const uploadModal = document.getElementById('documentUploadModal');
|
||||||
|
let isModalOpen = false;
|
||||||
|
|
||||||
|
// Open modal buttons
|
||||||
|
const openModalBtns = document.querySelectorAll('.upload-modal-trigger');
|
||||||
|
openModalBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (uploadModal) {
|
||||||
|
uploadModal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
isModalOpen = true;
|
||||||
|
// Initialize Lucide icons inside modal
|
||||||
|
setTimeout(() => lucide.createIcons(), 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal buttons (both close X and Cancel button)
|
||||||
|
document.querySelectorAll('.modal-close-btn, .modal-cancel-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (uploadModal && isModalOpen) {
|
||||||
|
uploadModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal when clicking outside
|
||||||
|
if (uploadModal) {
|
||||||
|
uploadModal.addEventListener('click', function(e) {
|
||||||
|
if (e.target === uploadModal && isModalOpen) {
|
||||||
|
uploadModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize first tab as active
|
// Close modal on escape key
|
||||||
showTab('contact-pane');
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && uploadModal && isModalOpen) {
|
||||||
// Modal close functionality
|
uploadModal.classList.add('hidden');
|
||||||
document.addEventListener('click', function(e) {
|
document.body.style.overflow = '';
|
||||||
const modal = document.getElementById('stageUpdateModal');
|
isModalOpen = false;
|
||||||
if (e.target === modal) {
|
|
||||||
modal.classList.add('hidden');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Stage Update Modal Functionality
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
const stageModal = document.getElementById('stageUpdateModal');
|
||||||
|
let isStageModalOpen = false;
|
||||||
|
const currentStage = '{{ application.stage }}';
|
||||||
|
|
||||||
|
// Open stage modal buttons
|
||||||
|
const openStageModalBtns = document.querySelectorAll('.stage-modal-trigger');
|
||||||
|
openStageModalBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (stageModal) {
|
||||||
|
stageModal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
isStageModalOpen = true;
|
||||||
|
// Initialize Lucide icons inside modal
|
||||||
|
setTimeout(() => lucide.createIcons(), 100);
|
||||||
|
// Set initial stage selection
|
||||||
|
const stageSelect = document.getElementById('id_stage');
|
||||||
|
if (stageSelect) {
|
||||||
|
stageSelect.value = currentStage;
|
||||||
|
// Call updateStageInfo if function exists
|
||||||
|
if (typeof updateStageInfo === 'function') {
|
||||||
|
updateStageInfo(currentStage, currentStage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stage selection change handler
|
||||||
|
const stageSelect = document.getElementById('id_stage');
|
||||||
|
if (stageSelect) {
|
||||||
|
stageSelect.addEventListener('change', function(e) {
|
||||||
|
const selectedValue = e.target.value;
|
||||||
|
if (typeof updateStageInfo === 'function') {
|
||||||
|
updateStageInfo(selectedValue, currentStage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stage modal buttons
|
||||||
|
document.querySelectorAll('#stageUpdateModal button').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (stageModal && isStageModalOpen) {
|
||||||
|
stageModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isStageModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close stage modal when clicking outside
|
||||||
|
if (stageModal) {
|
||||||
|
stageModal.addEventListener('click', function(e) {
|
||||||
|
if (e.target === stageModal || (e.target.classList.contains('fixed') && e.target.classList.contains('inset-0'))) {
|
||||||
|
stageModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isStageModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stage modal on escape key
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && stageModal && isStageModalOpen) {
|
||||||
|
stageModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isStageModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal after successful HTMX form submission
|
||||||
|
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
||||||
|
// Close document upload modal
|
||||||
|
if (evt.detail.successful && uploadModal && isModalOpen) {
|
||||||
|
const form = evt.detail.elt;
|
||||||
|
if (form && form.tagName === 'FORM' && form.action.includes('document_upload')) {
|
||||||
|
uploadModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isModalOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stage update modal
|
||||||
|
if (evt.detail.successful && stageModal && isStageModalOpen) {
|
||||||
|
const form = evt.detail.elt;
|
||||||
|
if (form && form.tagName === 'FORM' && form.action.includes('application_update_stage')) {
|
||||||
|
stageModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isStageModalOpen = false;
|
||||||
|
// Reload page to update stage display
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Reinitialize Lucide icons after HTMX updates
|
||||||
|
// ========================================
|
||||||
|
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||||
|
lucide.createIcons();
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -3,229 +3,74 @@
|
|||||||
|
|
||||||
{% block title %}{% trans "Update" %} {{ object.name }} - {{ block.super }}{% endblock %}
|
{% block title %}{% trans "Update" %} {{ object.name }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* UI Variables for the KAAT-S Theme */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-gray-light: #f8f9fa;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Container Styling */
|
|
||||||
.form-container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card Styling */
|
|
||||||
.card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Action Button Style */
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.4rem;
|
|
||||||
padding: 0.5rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary Button Style */
|
|
||||||
.btn-outline-secondary {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Field Styling */
|
|
||||||
.form-control:focus {
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-select:focus {
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Profile Image Upload Styling */
|
|
||||||
.profile-image-upload {
|
|
||||||
border: 2px dashed var(--kaauh-border);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-image-upload:hover {
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-image-preview {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 3px solid var(--kaauh-teal);
|
|
||||||
margin: 0 auto 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-image {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid var(--kaauh-teal);
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Breadcrumb Styling */
|
|
||||||
.breadcrumb {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumb-item + .breadcrumb-item::before {
|
|
||||||
content: ">";
|
|
||||||
color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Alert Styling */
|
|
||||||
.alert {
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading State */
|
|
||||||
.btn.loading {
|
|
||||||
position: relative;
|
|
||||||
pointer-events: none;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.loading::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin: auto;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
border-top-color: #ffffff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Current Profile Section */
|
|
||||||
.current-profile {
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-profile h6 {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="px-4 py-6 max-w-4xl mx-auto">
|
||||||
<div class="form-container">
|
|
||||||
<!-- Breadcrumb Navigation -->
|
<!-- Breadcrumb Navigation -->
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb" class="mb-6">
|
||||||
<ol class="breadcrumb">
|
<ol class="flex items-center space-x-2 text-sm">
|
||||||
<li class="breadcrumb-item">
|
<li>
|
||||||
<a href="{% url 'application_list' %}" class="text-decoration-none text-secondary">
|
<a href="{% url 'application_list' %}" class="text-gray-500 hover:text-temple-red transition">
|
||||||
<i class="fas fa-users me-1"></i> {% trans "Applications" %}
|
<i data-lucide="users" class="w-4 h-4 inline mr-1"></i> {% trans "Applications" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="breadcrumb-item">
|
<li class="text-gray-400">/</li>
|
||||||
<a href="{% url 'application_detail' object.slug %}" class="text-decoration-none text-secondary">
|
<li>
|
||||||
|
<a href="{% url 'application_detail' object.slug %}" class="text-gray-500 hover:text-temple-red transition">
|
||||||
{{ object.name }}
|
{{ object.name }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="breadcrumb-item active" aria-current="page"
|
<li class="text-temple-red font-semibold" aria-current="page">{% trans "Update" %}</li>
|
||||||
style="
|
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;">{% trans "Update" %}</li>
|
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6">
|
||||||
<h3 style="color: var(--kaauh-teal-dark);">
|
<h3 class="text-2xl font-bold text-temple-dark flex items-center gap-2">
|
||||||
<i class="fas fa-user-edit me-2"></i> {% trans "Update Application" %}
|
<i data-lucide="user-edit" class="w-6 h-6"></i>
|
||||||
|
{% trans "Update Application" %}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="d-flex gap-2">
|
<div class="flex gap-2">
|
||||||
<a href="{% url 'application_detail' object.slug %}" class="btn btn-outline-secondary">
|
<a href="{% url 'application_detail' object.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
|
{% trans "View Details" %}
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'application_delete' object.slug %}" class="btn btn-danger">
|
<a href="{% url 'application_delete' object.slug %}" class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
|
{% trans "Delete" %}
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'application_list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'application_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
{% trans "Back to List" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Current Profile Info -->
|
<!-- Current Profile Info -->
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200 mb-6">
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<div class="current-profile">
|
<div class="bg-gray-50 rounded-lg p-4">
|
||||||
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
|
<h6 class="text-sm font-semibold text-temple-dark mb-3 flex items-center gap-2">
|
||||||
<div class="d-flex align-items-center">
|
<i data-lucide="info" class="w-4 h-4"></i>
|
||||||
|
{% trans "Currently Editing" %}
|
||||||
|
</h6>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
{% if object.profile_image %}
|
{% if object.profile_image %}
|
||||||
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}"
|
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}"
|
||||||
class="current-image">
|
class="w-24 h-24 rounded-full border-2 border-temple-red object-cover">
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="current-image d-flex align-items-center justify-content-center bg-light">
|
<div class="w-24 h-24 rounded-full border-2 border-gray-300 bg-gray-100 flex items-center justify-center">
|
||||||
<i class="fas fa-user text-muted"></i>
|
<i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-1">{{ object.name }}</h5>
|
<h5 class="text-lg font-semibold text-gray-900 mb-1">{{ object.name }}</h5>
|
||||||
{% if object.email %}
|
{% if object.email %}
|
||||||
<p class="text-muted mb-0">{{ object.email }}</p>
|
<p class="text-gray-600 text-sm mb-1">{{ object.email }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<small class="text-muted">
|
<p class="text-gray-500 text-xs">
|
||||||
{% trans "Created" %}: {{ object.created_at|date:"d M Y" }} •
|
{% trans "Created" %}: {{ object.created_at|date:"d M Y" }} •
|
||||||
{% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }}
|
{% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }}
|
||||||
</small>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -233,24 +78,24 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Form Card -->
|
<!-- Form Card -->
|
||||||
<div class="card shadow-sm">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||||
<div class="card-body p-4">
|
<div class="p-6">
|
||||||
{% if form.non_field_errors %}
|
{% if form.non_field_errors %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4" role="alert">
|
||||||
<h5 class="alert-heading">
|
<h5 class="font-semibold text-red-800 flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
<i data-lucide="alert-triangle" class="w-5 h-5"></i>
|
||||||
|
{% trans "Error" %}
|
||||||
</h5>
|
</h5>
|
||||||
{% for error in form.non_field_errors %}
|
{% for error in form.non_field_errors %}
|
||||||
<p class="mb-0">{{ error }}</p>
|
<p class="text-red-700 text-sm">{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
<div class="rounded-lg p-4 mb-4 {% if message.tags == 'success' %}bg-green-50 border border-green-200 text-green-800{% elif message.tags == 'error' %}bg-red-50 border border-red-200 text-red-800{% else %}bg-blue-50 border border-blue-200 text-blue-800{% endif %}" role="alert">
|
||||||
{{ message }}
|
{{ message }}
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -259,20 +104,41 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
</form>
|
</form>
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<button form="candidate-form" type="submit" class="btn btn-main-action">
|
<div class="flex gap-2 mt-6">
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Update" %}
|
<button form="candidate-form" type="submit" class="bg-temple-red hover:bg-red-800 text-white font-semibold px-8 py-3 rounded-xl transition shadow-md hover:shadow-lg flex items-center gap-2">
|
||||||
|
<i data-lucide="save" class="w-5 h-5"></i>
|
||||||
|
{% trans "Update" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize Lucide icons
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
|
// Add form styling
|
||||||
|
const formInputs = document.querySelectorAll('input:not([type="hidden"]), select, textarea');
|
||||||
|
formInputs.forEach(input => {
|
||||||
|
input.classList.add('w-full', 'px-3', 'py-2.5', 'border', 'border-gray-300', 'rounded-lg', 'focus:ring-2', 'focus:ring-temple-red', 'focus:border-transparent', 'transition');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Style form labels and containers
|
||||||
|
const formGroups = document.querySelectorAll('.form-group');
|
||||||
|
formGroups.forEach(group => {
|
||||||
|
group.classList.add('mb-6');
|
||||||
|
});
|
||||||
|
|
||||||
|
const formLabels = document.querySelectorAll('label');
|
||||||
|
formLabels.forEach(label => {
|
||||||
|
label.classList.add('block', 'text-sm', 'font-semibold', 'text-gray-700', 'mb-2');
|
||||||
|
});
|
||||||
|
|
||||||
// Profile Image Preview
|
// Profile Image Preview
|
||||||
const profileImageInput = document.getElementById('id_profile_image');
|
const profileImageInput = document.getElementById('id_profile_image');
|
||||||
const imagePreviewContainer = document.getElementById('image-preview-container');
|
const imagePreviewContainer = document.getElementById('image-preview-container');
|
||||||
@ -286,9 +152,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
reader.onload = function(e) {
|
reader.onload = function(e) {
|
||||||
if (imagePreviewContainer) {
|
if (imagePreviewContainer) {
|
||||||
imagePreviewContainer.innerHTML = `
|
imagePreviewContainer.innerHTML = `
|
||||||
<img src="${e.target.result}" alt="Profile Preview" class="profile-image-preview">
|
<img src="${e.target.result}" alt="Profile Preview" class="w-32 h-32 rounded-full border-3 border-temple-red object-cover mx-auto mb-3">
|
||||||
<h5 class="text-muted mt-3">${file.name}</h5>
|
<h5 class="text-gray-600 text-sm font-medium">${file.name}</h5>
|
||||||
<p class="text-muted small">{% trans "New photo selected" %}</p>
|
<p class="text-gray-500 text-xs">{% trans "New photo selected" %}</p>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -305,15 +171,17 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener('submit', function(e) {
|
||||||
const submitBtn = form.querySelector('button[type="submit"]');
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
submitBtn.classList.add('loading');
|
|
||||||
|
// Add loading state
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 mr-2 animate-spin"></i> {% trans "Saving..." %}`;
|
||||||
|
|
||||||
// Basic validation
|
// Basic validation
|
||||||
const name = document.getElementById('id_name');
|
const name = document.getElementById('id_name');
|
||||||
if (name && !name.value.trim()) {
|
if (name && !name.value.trim()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitBtn.classList.remove('loading');
|
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Update" %}`;
|
||||||
alert('{% trans "Name is required." %}');
|
alert('{% trans "Name is required." %}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -321,8 +189,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const email = document.getElementById('id_email');
|
const email = document.getElementById('id_email');
|
||||||
if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
|
if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitBtn.classList.remove('loading');
|
|
||||||
submitBtn.disabled = false;
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Update" %}`;
|
||||||
alert('{% trans "Please enter a valid email address." %}');
|
alert('{% trans "Please enter a valid email address." %}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -337,9 +205,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Warn before leaving if changes are made
|
// Warn before leaving if changes are made
|
||||||
let formChanged = false;
|
let formChanged = false;
|
||||||
const formInputs = form ? form.querySelectorAll('input, select, textarea') : [];
|
const formInputsTrack = form ? form.querySelectorAll('input, select, textarea') : [];
|
||||||
|
|
||||||
formInputs.forEach(input => {
|
formInputsTrack.forEach(input => {
|
||||||
input.addEventListener('change', function() {
|
input.addEventListener('change', function() {
|
||||||
formChanged = true;
|
formChanged = true;
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,323 +1,182 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{% blocktrans %}Exam Stage- {{ job.title }} - ATS {% endblocktrans %}{% endblock %}
|
{% block title %}{% blocktrans %}Exam Stage - {{ job.title }} - University ATS{% endblocktrans %}{% endblock %}
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* KAAT-S UI Variables */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-success: #28a745;
|
|
||||||
--kaauh-info: #17a2b8;
|
|
||||||
--kaauh-danger: #dc3545;
|
|
||||||
--kaauh-warning: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Primary Color Overrides */
|
|
||||||
.text-primary-theme { color: var(--kaauh-teal) !important; }
|
|
||||||
.bg-primary-theme { background-color: var(--kaauh-teal) !important; }
|
|
||||||
|
|
||||||
/* 1. Main Container & Card Styling */
|
|
||||||
.kaauh-card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dedicated style for the filter block */
|
|
||||||
.filter-controls {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. Button Styling (Themed for Main Actions) */
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
/* Style for the Bulk Move button */
|
|
||||||
.btn-bulk-action {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.btn-bulk-action:hover {
|
|
||||||
background-color: #00363e;
|
|
||||||
border-color: #00363e;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3. Application Table Styling (Aligned with KAAT-S) */
|
|
||||||
.application-table {
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: separate;
|
|
||||||
border-spacing: 0;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.application-table thead {
|
|
||||||
background-color: var(--kaauh-border);
|
|
||||||
}
|
|
||||||
.application-table th {
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-bottom: 2px solid var(--kaauh-teal);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.application-table td {
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.application-table tbody tr:hover {
|
|
||||||
background-color: #f1f3f4;
|
|
||||||
}
|
|
||||||
.application-table thead th:nth-child(1) { width: 40px; }
|
|
||||||
.application-table thead th:nth-child(4) { width: 10%; }
|
|
||||||
.application-table thead th:nth-child(7) { width: 100px; }
|
|
||||||
|
|
||||||
.application-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
}
|
|
||||||
.application-details {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 4. Badges and Statuses */
|
|
||||||
.ai-score-badge {
|
|
||||||
background-color: var(--kaauh-teal-dark) !important;
|
|
||||||
color: white;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 0.4em 0.8em;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
}
|
|
||||||
.status-badge {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.3em 0.7em;
|
|
||||||
border-radius: 0.35rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.bg-applicant { background-color: #6c757d !important; color: white; }
|
|
||||||
.bg-application { background-color: var(--kaauh-success) !important; color: white; }
|
|
||||||
|
|
||||||
/* Stage Badges */
|
|
||||||
.stage-badge {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.25rem 0.6rem;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
font-weight: 600;
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 0.2rem;
|
|
||||||
}
|
|
||||||
.stage-Applied { background-color: #e9ecef; color: #495057; }
|
|
||||||
.stage-Screening { background-color: var(--kaauh-info); color: white; }
|
|
||||||
.stage-Exam { background-color: var(--kaauh-warning); color: #856404; }
|
|
||||||
.stage-Interview { background-color: #17a2b8; color: white; }
|
|
||||||
.stage-Offer { background-color: var(--kaauh-success); color: white; }
|
|
||||||
|
|
||||||
/* Timeline specific container */
|
|
||||||
.applicant-tracking-timeline {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- CUSTOM HEIGHT OPTIMIZATION (MAKING INPUTS/BUTTONS SMALLER) --- */
|
|
||||||
.form-control-sm,
|
|
||||||
.btn-sm {
|
|
||||||
/* Reduce vertical padding even more than default Bootstrap 'sm' */
|
|
||||||
padding-top: 0.2rem !important;
|
|
||||||
padding-bottom: 0.2rem !important;
|
|
||||||
/* Ensure a consistent, small height for both */
|
|
||||||
height: 28px !important;
|
|
||||||
font-size: 0.8rem !important; /* Slightly smaller font */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="p-3 sm:p-4 lg:p-8">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1 page-header">
|
<h1 class="text-2xl sm:text-3xl font-bold text-temple-dark mb-2 flex items-center gap-2">
|
||||||
<i class="fas fa-edit me-2"></i>
|
<i data-lucide="edit-3" class="w-6 h-6 sm:w-7 sm:h-7"></i>
|
||||||
{% trans "Exam Management" %} - {{ job.title }}
|
{% trans "Exam Management" %}
|
||||||
</h1>
|
</h1>
|
||||||
<h2 class="h5 text-muted mb-0">
|
<p class="text-gray-500 text-sm sm:text-base">
|
||||||
{% trans "Applications in Exam Stage:" %} <span class="fw-bold">{{ total_candidates }}</span>
|
{% trans "Job:" %} {{ job.title }}
|
||||||
</h2>
|
<span class="inline-block ml-2 px-2 py-0.5 bg-gray-200 text-gray-700 rounded-full text-xs font-normal">
|
||||||
|
{{ job.internal_job_id }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="flex gap-2 w-full sm:w-auto">
|
||||||
<a href="{% url 'export_applications_csv' job.slug 'exam' %}"
|
<a href="{% url 'export_applications_csv' job.slug 'exam' %}"
|
||||||
class="btn btn-outline-secondary"
|
class="flex items-center gap-2 px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium"
|
||||||
title="{% trans 'Export exam applications to CSV' %}">
|
title="{% trans 'Export exam applications to CSV' %}">
|
||||||
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %}
|
<i data-lucide="download" class="w-4 h-4"></i>
|
||||||
|
<span class="hidden sm:inline">{% trans "Export CSV" %}</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
|
<a href="{% url 'job_detail' job.slug %}"
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Job" %}
|
class="flex items-center gap-2 px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
<span class="hidden sm:inline">{% trans "Back to Job" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="applicant-tracking-timeline mb-4">
|
<!-- Applicant Tracking Timeline -->
|
||||||
|
<div class="mb-6">
|
||||||
{% include 'jobs/partials/applicant_tracking.html' %}
|
{% include 'jobs/partials/applicant_tracking.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="h4 mb-3" style="color: var(--kaauh-primary-text);">
|
<!-- Application List Header -->
|
||||||
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4 gap-3">
|
||||||
|
<h2 class="text-xl font-bold text-temple-dark flex items-center gap-2">
|
||||||
|
<i data-lucide="users" class="w-5 h-5"></i>
|
||||||
{% trans "Application List" %}
|
{% trans "Application List" %}
|
||||||
<span class="badge bg-primary-theme ms-2">{{ applications|length }} / {{ total_candidates }} Total</span>
|
<span class="ml-2 px-2 py-1 bg-temple-red text-white text-xs rounded-full">
|
||||||
<small class="text-muted fw-normal ms-2">({% trans "Sorted by AI Score" %})</small>
|
{{ applications|length }} / {{ total_candidates }} {% trans "Total" %}
|
||||||
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
<div class="text-xs text-gray-500">
|
||||||
|
{% trans "Sorted by AI Score" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="kaauh-card shadow-sm p-3">
|
<!-- Main Card -->
|
||||||
|
<div class="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
|
||||||
|
<!-- Bulk Action Bar -->
|
||||||
{% if applications %}
|
{% if applications %}
|
||||||
<div class="bulk-action-bar p-3 bg-light border-bottom">
|
<div class="p-3 sm:p-4 bg-gray-50 border-b border-gray-200">
|
||||||
<form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group">
|
<form hx-boost="true" hx-include="#application-form"
|
||||||
|
action="{% url 'application_update_status' job.slug %}"
|
||||||
|
method="post"
|
||||||
|
class="flex flex-col sm:flex-row gap-3 sm:gap-4 items-end">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{# Using d-flex for horizontal alignment and align-items-end to align items to the bottom baseline #}
|
<div class="flex-1 w-full sm:w-auto">
|
||||||
<div class="d-flex align-items-end gap-3">
|
<select name="mark_as" id="update_status"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent text-sm">
|
||||||
{# Select Input Group #}
|
<option selected>----------</option>
|
||||||
<div>
|
<option value="Interview">{% trans "Interview Stage" %}</option>
|
||||||
|
<option value="Applied">{% trans "Screening Stage" %}</option>
|
||||||
<select name="mark_as" id="update_status" class="form-select form-select-sm" style="min-width: 150px;">
|
|
||||||
<option selected>
|
|
||||||
----------
|
|
||||||
</option>
|
|
||||||
<option value="Interview">
|
|
||||||
{% trans "Interview Stage" %}
|
|
||||||
</option>
|
|
||||||
<option value="Applied">
|
|
||||||
{% trans "Screening Stage" %}
|
|
||||||
</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Button #}
|
<button id="changeStage" type="submit"
|
||||||
<button id="changeStage" type="submit" class="btn btn-main-action btn-sm">
|
class="w-full sm:w-auto px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium flex items-center justify-center gap-2">
|
||||||
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %}
|
<i data-lucide="arrow-right" class="w-4 h-4"></i>
|
||||||
|
{% trans "Change Stage" %}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button id="emailBotton" type="button" class="btn btn-outline-primary btn-sm"
|
<button id="emailBotton" type="button"
|
||||||
data-bs-toggle="modal"
|
class="w-full sm:w-auto px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium flex items-center justify-center gap-2"
|
||||||
hx-boost='true'
|
onclick="openEmailModal()"
|
||||||
data-bs-target="#emailModal"
|
title="{% trans 'Email Participants' %}">
|
||||||
hx-get="{% url 'compose_application_email' job.slug %}"
|
<i data-lucide="mail" class="w-4 h-4"></i>
|
||||||
hx-target="#emailModalBody"
|
|
||||||
hx-include="#application-form"
|
|
||||||
title="Email Participants">
|
|
||||||
<i class="fas fa-envelope"></i>
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="table-responsive">
|
|
||||||
<form id="application-form" method="post">
|
<!-- Table -->
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<form id="application-form" action="{% url 'application_update_status' job.slug %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table class="table application-table align-middle">
|
<table class="w-full border-collapse">
|
||||||
<thead>
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 2%;">
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[2%]">
|
||||||
{% if applications %}
|
{% if applications %}
|
||||||
<div class="form-check">
|
<div class="flex items-center">
|
||||||
<input
|
<input type="checkbox" class="h-4 w-4 text-temple-red rounded border-gray-300 focus:ring-temple-red" id="selectAllCheckbox">
|
||||||
type="checkbox" class="form-check-input" id="selectAllCheckbox">
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</th>
|
</th>
|
||||||
<th style="width: 15%;">{% trans "Name" %}</th>
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
||||||
<th style="width: 15%;">{% trans "Contact Info" %}</th>
|
<i data-lucide="user" class="w-3 h-3 inline mr-1"></i> {% trans "Name" %}
|
||||||
<th style="width: 10%;" class="text-center">{% trans "AI Score" %}</th>
|
</th>
|
||||||
<th style="width: 10%;">{% trans "Exam Date" %}</th>
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
||||||
<th style="width: 10%;">{% trans "Exam Score" %}</th>
|
<i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {% trans "Contact Info" %}
|
||||||
<th style="width: 10%;" class="text-center">{% trans "Exam Results" %}</th>
|
</th>
|
||||||
<th style="width: 10%"> {% trans "Notes"%}</th>
|
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
|
||||||
<th style="width: 15%;">{% trans "Actions" %}</th>
|
<i data-lucide="bot" class="w-3 h-3 inline mr-1"></i> {% trans "AI Score" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
|
||||||
|
{% trans "Exam Date" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
|
||||||
|
{% trans "Exam Score" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
|
||||||
|
{% trans "Exam Results" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
|
||||||
|
{% trans "Notes" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[18%]">
|
||||||
|
<i data-lucide="settings" class="w-3 h-3 inline mr-1"></i> {% trans "Actions" %}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for application in applications %}
|
{% for application in applications %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
<td>
|
<td class="px-4 py-3 border-b border-gray-200">
|
||||||
<div class="form-check">
|
<div class="flex items-center">
|
||||||
<input
|
<input name="candidate_ids" value="{{ application.id }}"
|
||||||
name="candidate_ids"
|
type="checkbox"
|
||||||
value="{{ application.id }}"
|
class="h-4 w-4 text-temple-red rounded border-gray-300 focus:ring-temple-red rowCheckbox"
|
||||||
type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
|
id="application-{{ application.id }}">
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3 border-b border-gray-200">
|
||||||
<div class="application-name">
|
<div class="font-semibold text-temple-red">
|
||||||
{{ application.name }}
|
{{ application.name }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3 border-b border-gray-200">
|
||||||
<div class="application-details">
|
<div class="text-xs text-gray-500">
|
||||||
<i class="fas fa-envelope me-1"></i> {{ application.email }}<br>
|
<i data-lucide="mail" class="w-3 h-3 inline mr-1"></i> {{ application.email }}<br>
|
||||||
<i class="fas fa-phone me-1"></i> {{ application.phone }}
|
<i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {{ application.phone }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="px-4 py-3 text-center border-b border-gray-200">
|
||||||
<span class="badge ai-score-badge">{{ application.match_score|default:"0" }}%</span>
|
<span class="inline-block px-2 py-1 bg-temple-red text-white text-xs font-bold rounded-md">
|
||||||
|
{{ application.match_score|default:"0" }}%
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3 border-b border-gray-200 text-sm">
|
||||||
{{application.exam_date|date:"d-m-Y h:i A"|default:"--"}}
|
{{application.exam_date|date:"d-m-Y h:i A"|default:"--"}}
|
||||||
</td>
|
</td>
|
||||||
<td id="exam-score-{{ application.pk}}">
|
<td class="px-4 py-3 border-b border-gray-200 text-sm" id="exam-score-{{ application.pk }}">
|
||||||
{{application.exam_score|default:"--"}}
|
{{application.exam_score|default:"--"}}
|
||||||
</td>
|
</td>
|
||||||
|
<td class="px-4 py-3 text-center border-b border-gray-200" id="status-result-{{ application.pk }}">
|
||||||
<td class="text-center" id="status-result-{{ application.pk}}">
|
|
||||||
{% if not application.exam_status %}
|
{% if not application.exam_status %}
|
||||||
<button type="button" class="btn btn-warning btn-sm"
|
<button type="button"
|
||||||
data-bs-toggle="modal"
|
class="px-3 py-1.5 bg-yellow-400 hover:bg-yellow-500 text-yellow-900 rounded-lg transition text-xs font-medium flex items-center gap-1 mx-auto"
|
||||||
data-bs-target="#candidateviewModal"
|
onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}')"
|
||||||
hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}"
|
title="{% trans 'Pass Exam' %}">
|
||||||
hx-target="#candidateviewModalBody"
|
<i data-lucide="plus" class="w-3 h-3"></i>
|
||||||
title="Pass Exam">
|
|
||||||
<i class="fas fa-plus"></i>
|
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if application.exam_status %}
|
{% if application.exam_status %}
|
||||||
<button type="button" class="btn btn-{% if application.exam_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
|
<button type="button"
|
||||||
data-bs-toggle="modal"
|
class="px-3 py-1.5 {% if application.exam_status == 'Passed' %}bg-green-500 hover:bg-green-600 text-white{% else %}bg-red-500 hover:bg-red-600 text-white{% endif %} rounded-lg transition text-xs font-medium"
|
||||||
data-bs-target="#candidateviewModal"
|
onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}')"
|
||||||
hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}"
|
title="{% trans 'Update Exam Status' %}">
|
||||||
hx-target="#candidateviewModalBody"
|
|
||||||
title="Pass Exam">
|
|
||||||
{{ application.exam_status }}
|
{{ application.exam_status }}
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -325,34 +184,31 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td><button type="button" class="btn btn-outline-primary btn-sm"
|
<td class="px-4 py-3 border-b border-gray-200">
|
||||||
data-bs-toggle="modal"
|
<button type="button"
|
||||||
data-bs-target="#noteModal"
|
class="px-3 py-1.5 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-xs font-medium flex items-center gap-1 mx-auto"
|
||||||
hx-get="{% url 'application_add_note' application.slug %}"
|
onclick="openNoteModal('{% url 'application_add_note' application.slug %}')">
|
||||||
hx-swap="innerHTML"
|
<i data-lucide="plus-circle" class="w-3 h-3"></i>
|
||||||
hx-target=".notemodal">
|
{% trans "Add note" %}
|
||||||
<i class="fas fa-calendar-plus me-1"></i>
|
</button>
|
||||||
Add note
|
</td>
|
||||||
</button></td>
|
<td class="px-4 py-3 border-b border-gray-200 text-center">
|
||||||
|
<button type="button"
|
||||||
<td >
|
class="px-3 py-1.5 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-xs font-medium"
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
onclick="openCandidateModal('{% url 'application_criteria_view_htmx' application.pk %}')"
|
||||||
data-bs-toggle="modal"
|
title="{% trans 'View Application Profile' %}">
|
||||||
data-bs-target="#candidateviewModal"
|
<i data-lucide="eye" class="w-3 h-3"></i>
|
||||||
hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
|
|
||||||
hx-target="#candidateviewModalBody"
|
|
||||||
title="View Profile">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{% if not applications %}
|
{% if not applications %}
|
||||||
<div class="alert alert-info text-center mt-3 mb-0" role="alert">
|
<div class="p-8 text-center bg-blue-50 border border-blue-200 rounded-lg m-4">
|
||||||
<i class="fas fa-info-circle me-1"></i>
|
<i data-lucide="info" class="w-8 h-8 text-blue-500 mx-auto mb-2"></i>
|
||||||
{% trans "No applications are currently in the Exam stage for this job." %}
|
<p class="text-blue-700 text-sm">{% trans "No applications are currently in the Exam stage for this job." %}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</form>
|
</form>
|
||||||
@ -360,23 +216,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal fade modal-lg" id="candidateviewModal" tabindex="-1" aria-labelledby="candidateviewModalLabel" aria-hidden="true">
|
<!-- Candidate View Modal -->
|
||||||
<div class="modal-dialog">
|
<div id="candidateviewModal" class="fixed inset-0 z-50 hidden" aria-labelledby="candidateviewModalLabel" role="dialog" aria-modal="true">
|
||||||
<div class="modal-content kaauh-card">
|
<!-- Backdrop -->
|
||||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeCandidateModal()"></div>
|
||||||
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
|
|
||||||
|
<!-- Modal Content -->
|
||||||
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||||
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-dark" id="candidateviewModalLabel">
|
||||||
{% trans "Application Details & Exam Update" %}
|
{% trans "Application Details & Exam Update" %}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeCandidateModal()" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="candidateviewModalBody" class="modal-body">
|
<div id="candidateviewModalBody" class="flex-1 overflow-y-auto p-6">
|
||||||
<div class="text-center py-5 text-muted">
|
<div class="text-center py-10 text-gray-500">
|
||||||
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
{% trans "Loading application data..." %}
|
<p>{% trans "Loading application data..." %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer" style="border-top: 1px solid var(--kaauh-border);">
|
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
<button type="button" class="w-full px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeCandidateModal()">
|
||||||
{% trans "Close" %}
|
{% trans "Close" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -384,47 +247,260 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Email Modal -->
|
<!-- Email Modal -->
|
||||||
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
|
<div id="emailModal" class="fixed inset-0 z-50 hidden" aria-labelledby="emailModalLabel" role="dialog" aria-modal="true">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<!-- Backdrop -->
|
||||||
<div class="modal-content kaauh-card">
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeEmailModal()"></div>
|
||||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
|
||||||
<h5 class="modal-title" id="emailModalLabel" style="color: var(--kaauh-teal-dark);">
|
|
||||||
<i class="fas fa-envelope me-2"></i>{% trans "Compose Email" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div id="emailModalBody" class="modal-body">
|
|
||||||
<div class="text-center py-5 text-muted">
|
|
||||||
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
|
|
||||||
{% trans "Loading email form..." %}
|
|
||||||
|
|
||||||
|
<!-- Modal Content -->
|
||||||
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||||
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-dark" id="emailModalLabel">
|
||||||
|
<i data-lucide="mail" class="w-5 h-5 inline mr-2"></i>{% trans "Compose Email" %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeEmailModal()" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="emailModalBody" class="flex-1 overflow-y-auto p-6">
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading email form..." %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% include "recruitment/partials/note_modal.html" %}
|
|
||||||
{% include "recruitment/partials/stage_confirmation_modal.html" %}
|
<!-- Note Modal -->
|
||||||
|
<div id="noteModal" class="fixed inset-0 z-50 hidden" aria-labelledby="noteModalLabel" role="dialog" aria-modal="true">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeNoteModal()"></div>
|
||||||
|
|
||||||
|
<!-- Modal Content -->
|
||||||
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||||
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-dark" id="noteModalLabel">
|
||||||
|
<i data-lucide="sticky-note" class="w-5 h-5 inline mr-2"></i>{% trans "Add Note" %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeNoteModal()" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="noteModalBody" class="flex-1 overflow-y-auto p-6">
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading note form..." %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stage Confirmation Modal -->
|
||||||
|
<div id="stageConfirmationModal" class="fixed inset-0 z-50 hidden" aria-labelledby="stageConfirmationModalLabel" role="dialog" aria-modal="true">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeStageConfirmationModal()"></div>
|
||||||
|
|
||||||
|
<!-- Modal Content -->
|
||||||
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-lg">
|
||||||
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-dark" id="stageConfirmationModalLabel">
|
||||||
|
<i data-lucide="info" class="w-5 h-5 inline mr-2"></i>{% trans "Confirm Stage Change" %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeStageConfirmationModal()" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex items-center justify-center py-3 mb-3">
|
||||||
|
<i data-lucide="arrow-right-left" class="w-16 h-16 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-center mb-2 text-base text-gray-800">
|
||||||
|
<span id="stageConfirmationMessage">{% trans "Are you sure you want to change to this stage?" %}</span>
|
||||||
|
</p>
|
||||||
|
<div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg text-center" role="alert">
|
||||||
|
<i data-lucide="user-check" class="w-4 h-4 inline mr-2"></i>
|
||||||
|
<span class="font-semibold">{% trans "Selected Stage:" %}</span>
|
||||||
|
<span id="targetStageName" class="font-bold">{% trans "--" %}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-4 border-t border-gray-200 flex justify-end gap-2">
|
||||||
|
<button type="button" class="px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeStageConfirmationModal()">
|
||||||
|
<i data-lucide="x" class="w-4 h-4 inline mr-1"></i>{% trans "Cancel" %}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium" id="confirmStageChangeButton">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 inline mr-1"></i>{% trans "Confirm" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
|
// Reinitialize Lucide icons after content loads
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Modal Functions
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
function openCandidateModal(url) {
|
||||||
|
const modal = document.getElementById('candidateviewModal');
|
||||||
|
const modalBody = document.getElementById('candidateviewModalBody');
|
||||||
|
|
||||||
|
// Reset content
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading application data..." %}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Load content via HTMX
|
||||||
|
if (url && typeof htmx !== 'undefined') {
|
||||||
|
htmx.ajax('GET', url, {target: '#candidateviewModalBody', swap: 'innerHTML'});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinitialize icons
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCandidateModal() {
|
||||||
|
const modal = document.getElementById('candidateviewModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEmailModal() {
|
||||||
|
const modal = document.getElementById('emailModal');
|
||||||
|
const modalBody = document.getElementById('emailModalBody');
|
||||||
|
const applicationForm = document.getElementById('application-form');
|
||||||
|
const url = '{% url "compose_application_email" job.slug %}';
|
||||||
|
|
||||||
|
// Reset content
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading email form..." %}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Load content via HTMX with form data
|
||||||
|
if (url && typeof htmx !== 'undefined' && applicationForm) {
|
||||||
|
htmx.ajax('GET', url, {
|
||||||
|
target: '#emailModalBody',
|
||||||
|
swap: 'innerHTML',
|
||||||
|
values: htmx.values(applicationForm)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinitialize icons
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEmailModal() {
|
||||||
|
const modal = document.getElementById('emailModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openNoteModal(url) {
|
||||||
|
const modal = document.getElementById('noteModal');
|
||||||
|
const modalBody = document.getElementById('noteModalBody');
|
||||||
|
|
||||||
|
// Reset content
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading note form..." %}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Load content via HTMX
|
||||||
|
if (url && typeof htmx !== 'undefined') {
|
||||||
|
htmx.ajax('GET', url, {target: '#noteModalBody', swap: 'innerHTML'});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinitialize icons
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeNoteModal() {
|
||||||
|
const modal = document.getElementById('noteModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openStageConfirmationModal(selectedStage) {
|
||||||
|
const modal = document.getElementById('stageConfirmationModal');
|
||||||
|
const messageElement = document.getElementById('stageConfirmationMessage');
|
||||||
|
const targetStageElement = document.getElementById('targetStageName');
|
||||||
|
|
||||||
|
// Update confirmation message
|
||||||
|
if (messageElement && targetStageElement) {
|
||||||
|
const checkedCount = Array.from(document.querySelectorAll('.rowCheckbox:checked')).length;
|
||||||
|
if (checkedCount > 0) {
|
||||||
|
messageElement.textContent = `{% trans "Are you sure you want to move" %} ${checkedCount} {% trans "candidate(s) to this stage?" %}`;
|
||||||
|
targetStageElement.textContent = selectedStage;
|
||||||
|
} else {
|
||||||
|
messageElement.textContent = '{% trans "Please select at least one candidate." %}';
|
||||||
|
targetStageElement.textContent = '--';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeStageConfirmationModal() {
|
||||||
|
const modal = document.getElementById('stageConfirmationModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Checkbox and Form Logic
|
||||||
|
// ========================================
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||||
const rowCheckboxes = document.querySelectorAll('.rowCheckbox');
|
const rowCheckboxes = document.querySelectorAll('.rowCheckbox');
|
||||||
const changeStageButton = document.getElementById('changeStage');
|
const changeStageButton = document.getElementById('changeStage');
|
||||||
const emailButton = document.getElementById('emailBotton');
|
const emailButton = document.getElementById('emailBotton');
|
||||||
const updateStatus = document.getElementById('update_status');
|
const updateStatus = document.getElementById('update_status');
|
||||||
const stageConfirmationModal = new bootstrap.Modal(document.getElementById('stageConfirmationModal'));
|
|
||||||
const confirmStageChangeButton = document.getElementById('confirmStageChangeButton');
|
const confirmStageChangeButton = document.getElementById('confirmStageChangeButton');
|
||||||
let isConfirmed = false;
|
let isConfirmed = false;
|
||||||
|
|
||||||
if (selectAllCheckbox) {
|
if (selectAllCheckbox) {
|
||||||
|
|
||||||
// Function to safely update the header checkbox state
|
// Function to safely update the header checkbox state
|
||||||
function updateSelectAllState() {
|
function updateSelectAllState() {
|
||||||
const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
|
const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
|
||||||
@ -433,26 +509,25 @@
|
|||||||
if (checkedCount === 0) {
|
if (checkedCount === 0) {
|
||||||
selectAllCheckbox.checked = false;
|
selectAllCheckbox.checked = false;
|
||||||
selectAllCheckbox.indeterminate = false;
|
selectAllCheckbox.indeterminate = false;
|
||||||
changeStageButton.disabled = true;
|
if (changeStageButton) changeStageButton.disabled = true;
|
||||||
emailButton.disabled = true;
|
if (emailButton) emailButton.disabled = true;
|
||||||
updateStatus.disabled = true;
|
if (updateStatus) updateStatus.disabled = true;
|
||||||
} else if (checkedCount === totalCount) {
|
} else if (checkedCount === totalCount) {
|
||||||
selectAllCheckbox.checked = true;
|
selectAllCheckbox.checked = true;
|
||||||
selectAllCheckbox.indeterminate = false;
|
selectAllCheckbox.indeterminate = false;
|
||||||
changeStageButton.disabled = false;
|
if (changeStageButton) changeStageButton.disabled = false;
|
||||||
emailButton.disabled = false;
|
if (emailButton) emailButton.disabled = false;
|
||||||
updateStatus.disabled = false;
|
if (updateStatus) updateStatus.disabled = false;
|
||||||
} else {
|
} else {
|
||||||
// Set to indeterminate state (partially checked)
|
|
||||||
selectAllCheckbox.checked = false;
|
selectAllCheckbox.checked = false;
|
||||||
selectAllCheckbox.indeterminate = true;
|
selectAllCheckbox.indeterminate = true;
|
||||||
changeStageButton.disabled = false;
|
if (changeStageButton) changeStageButton.disabled = false;
|
||||||
emailButton.disabled = false;
|
if (emailButton) emailButton.disabled = false;
|
||||||
updateStatus.disabled = false;
|
if (updateStatus) updateStatus.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Logic for the 'Select All' checkbox (Clicking it updates all rows)
|
// Logic for 'Select All' checkbox
|
||||||
selectAllCheckbox.addEventListener('change', function () {
|
selectAllCheckbox.addEventListener('change', function () {
|
||||||
const isChecked = selectAllCheckbox.checked;
|
const isChecked = selectAllCheckbox.checked;
|
||||||
|
|
||||||
@ -467,12 +542,10 @@
|
|||||||
updateSelectAllState();
|
updateSelectAllState();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Logic to update 'Select All' state based on row checkboxes
|
|
||||||
rowCheckboxes.forEach(function (checkbox) {
|
rowCheckboxes.forEach(function (checkbox) {
|
||||||
checkbox.addEventListener('change', updateSelectAllState);
|
checkbox.addEventListener('change', updateSelectAllState);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initial check to set the correct state on load (in case items are pre-checked)
|
|
||||||
updateSelectAllState();
|
updateSelectAllState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,51 +554,34 @@
|
|||||||
changeStageButton.addEventListener('click', function(event) {
|
changeStageButton.addEventListener('click', function(event) {
|
||||||
const selectedStage = updateStatus.value;
|
const selectedStage = updateStatus.value;
|
||||||
|
|
||||||
// Check if a stage is selected (not default empty option)
|
|
||||||
if (selectedStage && selectedStage.trim() !== '') {
|
if (selectedStage && selectedStage.trim() !== '') {
|
||||||
// If not yet confirmed, show modal and prevent submission
|
|
||||||
if (!isConfirmed) {
|
if (!isConfirmed) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
openStageConfirmationModal(selectedStage);
|
||||||
// Count selected candidates
|
|
||||||
const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
|
|
||||||
|
|
||||||
// Update confirmation message
|
|
||||||
const messageElement = document.getElementById('stageConfirmationMessage');
|
|
||||||
const targetStageElement = document.getElementById('targetStageName');
|
|
||||||
if (messageElement && targetStageElement) {
|
|
||||||
if (checkedCount > 0) {
|
|
||||||
messageElement.textContent = `{% trans "Are you sure you want to move" %} ${checkedCount} {% trans "candidate(s) to this stage?" %}`;
|
|
||||||
targetStageElement.textContent = selectedStage;
|
|
||||||
} else {
|
|
||||||
messageElement.textContent = '{% trans "Please select at least one candidate." %}';
|
|
||||||
targetStageElement.textContent = '--';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show confirmation modal
|
|
||||||
stageConfirmationModal.show();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// If confirmed, let's form submit normally (reset flag for next time)
|
|
||||||
isConfirmed = false;
|
isConfirmed = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle confirm button click in modal
|
|
||||||
if (confirmStageChangeButton) {
|
if (confirmStageChangeButton) {
|
||||||
confirmStageChangeButton.addEventListener('click', function() {
|
confirmStageChangeButton.addEventListener('click', function() {
|
||||||
// Hide modal
|
closeStageConfirmationModal();
|
||||||
stageConfirmationModal.hide();
|
|
||||||
|
|
||||||
// Set confirmed flag
|
|
||||||
isConfirmed = true;
|
isConfirmed = true;
|
||||||
|
|
||||||
// Programmatically trigger's button click to submit form
|
|
||||||
changeStageButton.click();
|
changeStageButton.click();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close modals on escape key
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeCandidateModal();
|
||||||
|
closeEmailModal();
|
||||||
|
closeNoteModal();
|
||||||
|
closeStageConfirmationModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,438 +1,259 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "Hired Stage" %} {{ job.title }} - ATS{% endblock %}
|
{% block title %}{% blocktrans %}Hired Stage - {{ job.title }} - University ATS{% endblocktrans %}{% endblock %}
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* KAAT-S UI Variables */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-success: #28a745;
|
|
||||||
--kaauh-info: #17a2b8;
|
|
||||||
--kaauh-danger: #dc3545;
|
|
||||||
--kaauh-warning: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Primary Color Overrides */
|
|
||||||
.text-primary-theme { color: var(--kaauh-teal) !important; }
|
|
||||||
.bg-primary-theme { background-color: var(--kaauh-teal) !important; }
|
|
||||||
|
|
||||||
/* 1. Main Container & Card Styling */
|
|
||||||
.kaauh-card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dedicated style for the filter block */
|
|
||||||
.filter-controls {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 2. Button Styling (Themed for Main Actions) */
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.btn-main-action:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3. Application Table Styling (Aligned with KAAT-S) */
|
|
||||||
.application-table {
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: separate;
|
|
||||||
border-spacing: 0;
|
|
||||||
background-color: white;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.application-table thead {
|
|
||||||
background-color: var(--kaauh-border);
|
|
||||||
}
|
|
||||||
.application-table th {
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-bottom: 2px solid var(--kaauh-teal);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
.application-table td {
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
border-bottom: 1px solid var(--kaauh-border);
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
.application-table tbody tr:hover {
|
|
||||||
background-color: #f1f3f4;
|
|
||||||
}
|
|
||||||
.application-table thead th:nth-child(1) { width: 40px; }
|
|
||||||
.application-table thead th:nth-child(4) { width: 10%; }
|
|
||||||
.application-table thead th:nth-child(7) { width: 100px; }
|
|
||||||
|
|
||||||
.application-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
}
|
|
||||||
.application-details {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 4. Badges and Statuses */
|
|
||||||
.ai-score-badge {
|
|
||||||
background-color: var(--kaauh-teal-dark) !important;
|
|
||||||
color: white;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 0.4em 0.8em;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
}
|
|
||||||
.status-badge {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.3em 0.7em;
|
|
||||||
border-radius: 0.35rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.bg-applicant { background-color: #6c757d !important; color: white; }
|
|
||||||
.bg-application { background-color: var(--kaauh-success) !important; color: white; }
|
|
||||||
|
|
||||||
/* Stage Badges */
|
|
||||||
.stage-badge {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
padding: 0.25rem 0.6rem;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
font-weight: 600;
|
|
||||||
display: inline-block;
|
|
||||||
margin-bottom: 0.2rem;
|
|
||||||
}
|
|
||||||
.stage-Applied { background-color: #e9ecef; color: #495057; }
|
|
||||||
.stage-Screening { background-color: var(--kaauh-info); color: white; }
|
|
||||||
.stage-Exam { background-color: var(--kaauh-warning); color: #856404; }
|
|
||||||
.stage-Interview { background-color: #17a2b8; color: white; }
|
|
||||||
.stage-Offer { background-color: var(--kaauh-success); color: white; }
|
|
||||||
.stage-Hired { background-color: #28a745; color: white; }
|
|
||||||
|
|
||||||
/* Timeline specific container */
|
|
||||||
.applicant-tracking-timeline {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hired-specific styling */
|
|
||||||
.hired-badge {
|
|
||||||
background-color: #28a745 !important;
|
|
||||||
color: white;
|
|
||||||
font-weight: 700;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hired-date {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #6c757d;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Success state styling */
|
|
||||||
.success-header {
|
|
||||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
|
||||||
color: white;
|
|
||||||
padding: 1.5rem;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- CUSTOM HEIGHT OPTIMIZATION (MAKING INPUTS/BUTTONS SMALLER) --- */
|
|
||||||
.form-control-sm,
|
|
||||||
.btn-sm {
|
|
||||||
/* Reduce vertical padding even more than default Bootstrap 'sm' */
|
|
||||||
padding-top: 0.2rem !important;
|
|
||||||
padding-bottom: 0.2rem !important;
|
|
||||||
/* Ensure a consistent, small height for both */
|
|
||||||
height: 28px !important;
|
|
||||||
font-size: 0.8rem !important; /* Slightly smaller font */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="p-3 sm:p-4 lg:p-8">
|
||||||
|
<!-- Page Header -->
|
||||||
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-6 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 class="text-2xl sm:text-3xl font-bold text-temple-dark mb-2 flex items-center gap-2">
|
||||||
<i class="fas fa-trophy me-2"></i>
|
<i data-lucide="trophy" class="w-6 h-6 sm:w-7 sm:h-7"></i>
|
||||||
{% trans "Hired Applications" %} - {{ job.title }}
|
{% trans "Hired Applications" %} - {{ job.title }}
|
||||||
</h1>
|
</h1>
|
||||||
<h2 class="h5 text-muted mb-0">
|
<h2 class="text-sm sm:text-base text-gray-500 mb-0">
|
||||||
{% trans "Successfully Hired:" %} <span class="fw-bold">{{ applications|length }}</span>
|
{% trans "Successfully Hired:" %} <span class="font-bold">{{ applications|length }}</span>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="flex gap-2 w-full sm:w-auto">
|
||||||
<a href="{% url 'export_applications_csv' job.slug 'hired' %}"
|
<a href="{% url 'export_applications_csv' job.slug 'hired' %}"
|
||||||
class="btn btn-outline-secondary"
|
class="flex items-center gap-2 px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium"
|
||||||
title="{% trans 'Export hired applications to CSV' %}">
|
title="{% trans 'Export hired applications to CSV' %}">
|
||||||
<i class="fas fa-download me-1"></i> {% trans "Export CSV" %}
|
<i data-lucide="download" class="w-4 h-4"></i>
|
||||||
|
<span class="hidden sm:inline">{% trans "Export CSV" %}</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'job_detail' job.slug %}" class="btn btn-outline-secondary">
|
<a href="{% url 'job_detail' job.slug %}"
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Job" %}
|
class="flex items-center gap-2 px-4 py-2 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-sm font-medium">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
<span class="hidden sm:inline">{% trans "Back to Job" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Success Header -->
|
<!-- Success Header -->
|
||||||
<div class="success-header">
|
<div class="bg-gradient-to-br from-green-500 to-teal-500 text-white p-6 rounded-xl mb-6 text-center shadow-lg">
|
||||||
<i class="fas fa-check-circle fa-3x mb-3"></i>
|
<i data-lucide="check-circle" class="w-12 h-12 mb-3 mx-auto"></i>
|
||||||
<h3 class="mb-2">{% trans "Congratulations!" %}</h3>
|
<h3 class="text-xl font-bold mb-2">{% trans "Congratulations!" %}</h3>
|
||||||
<p class="mb-0">{% trans "These applications have successfully completed the hiring process and joined your team." %}</p>
|
<p class="mb-0 opacity-90">{% trans "These applications have successfully completed the hiring process and joined your team." %}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ERP Sync Status -->
|
<!-- ERP Sync Status -->
|
||||||
{% if job.source %}
|
{% if job.source %}
|
||||||
<div class="kaauh-card shadow-sm p-3 mb-4">
|
<div class="bg-white border border-gray-200 rounded-xl shadow-sm p-4 sm:p-6 mb-6">
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-2" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h5 class="text-lg font-bold text-temple-dark mb-4 flex items-center gap-2">
|
||||||
<i class="fas fa-database me-2"></i> {% trans "ERP Sync Status" %}
|
<i data-lucide="database" class="w-5 h-5"></i> {% trans "ERP Sync Status" %}
|
||||||
</h5>
|
</h5>
|
||||||
<div class="row g-3">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
|
||||||
<div class="col-md-3">
|
<div>
|
||||||
<small class="text-muted d-block">{% trans "Source:" %}</small>
|
<small class="text-gray-500 block text-xs">{% trans "Source:" %}</small>
|
||||||
<strong>{{ job.source.name }}</strong>
|
<strong class="text-sm">{{ job.source.name }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div>
|
||||||
<small class="text-muted d-block">{% trans "Sync Status:" %}</small>
|
<small class="text-gray-500 block text-xs">{% trans "Sync Status:" %}</small>
|
||||||
<span class="badge
|
<span class="px-2 py-1 rounded-full text-xs font-bold
|
||||||
{% if job.source.sync_status == 'SUCCESS' %}bg-success
|
{% if job.source.sync_status == 'SUCCESS' %}bg-green-500 text-white
|
||||||
{% elif job.source.sync_status == 'SYNCING' %}bg-warning
|
{% elif job.source.sync_status == 'SYNCING' %}bg-yellow-400 text-yellow-900
|
||||||
{% elif job.source.sync_status == 'ERROR' %}bg-danger
|
{% elif job.source.sync_status == 'ERROR' %}bg-red-500 text-white
|
||||||
{% else %}bg-secondary{% endif %}">
|
{% else %}bg-gray-400 text-white{% endif %}">
|
||||||
{{ job.source.get_sync_status_display }}
|
{{ job.source.get_sync_status_display }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div>
|
||||||
<small class="text-muted d-block">{% trans "Last Sync:" %}</small>
|
<small class="text-gray-500 block text-xs">{% trans "Last Sync:" %}</small>
|
||||||
<strong>
|
<strong class="text-sm">
|
||||||
{% if job.source.last_sync_at %}
|
{% if job.source.last_sync_at %}
|
||||||
{{ job.source.last_sync_at|date:"M d, Y H:i" }}
|
{{ job.source.last_sync_at|date:"M d, Y H:i" }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">{% trans "Never" %}</span>
|
<span class="text-gray-400">{% trans "Never" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div>
|
||||||
<small class="text-muted d-block">{% trans "Hired Candidates:" %}</small>
|
<small class="text-gray-500 block text-xs">{% trans "Hired Candidates:" %}</small>
|
||||||
<strong>{{ applications|length }}</strong>
|
<strong class="text-sm">{{ applications|length }}</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{# Manual sync button commented out - sync is now automatic via Django signals #}
|
|
||||||
{# <button type="button"
|
|
||||||
class="btn btn-main-action"
|
|
||||||
onclick="syncHiredCandidates()"
|
|
||||||
title="{% trans 'Manually sync hired applications to ERP source (use for re-syncs)' %}">
|
|
||||||
<i class="fas fa-sync me-1"></i> {% trans "Sync to Sources" %}
|
|
||||||
</button> #}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="alert alert-info mt-3 mb-0" style="font-size: 0.85rem;">
|
<div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg mt-4 text-sm" role="alert">
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
<i data-lucide="info" class="w-4 h-4 inline mr-2"></i>
|
||||||
{% trans "ERP sync is automatically triggered when candidates are moved to 'Hired' stage. Use the 'Sync to Sources' button for manual re-syncs if needed." %}
|
{% trans "ERP sync is automatically triggered when candidates are moved to 'Hired' stage." %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-warning mb-4">
|
<div class="bg-yellow-50 border border-yellow-200 text-yellow-800 px-4 py-3 rounded-lg mb-6 text-sm" role="alert">
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
<i data-lucide="alert-triangle" class="w-4 h-4 inline mr-2"></i>
|
||||||
{% trans "No ERP source configured for this job. Automatic sync is disabled." %}
|
{% trans "No ERP source configured for this job. Automatic sync is disabled." %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="applicant-tracking-timeline">
|
<!-- Applicant Tracking Timeline -->
|
||||||
|
<div class="mb-6">
|
||||||
{% include 'jobs/partials/applicant_tracking.html' %}
|
{% include 'jobs/partials/applicant_tracking.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="kaauh-card shadow-sm p-3">
|
<!-- Application List Header -->
|
||||||
{% comment %} {% if applications %}
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4 gap-3">
|
||||||
<div class="bulk-action-bar p-3 bg-light border-bottom">
|
<h2 class="text-xl font-bold text-temple-dark flex items-center gap-2">
|
||||||
<form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group">
|
<i data-lucide="users" class="w-5 h-5"></i>
|
||||||
{% csrf_token %}
|
{% trans "Hired Applications" %}
|
||||||
|
<span class="ml-2 px-2 py-1 bg-temple-red text-white text-xs rounded-full">
|
||||||
{# MODIFIED: Using d-flex for horizontal alignment and align-items-end to align everything based on the baseline of the button/select #}
|
{{ applications|length }}
|
||||||
<div class="d-flex align-items-end gap-3">
|
</span>
|
||||||
|
</h2>
|
||||||
{# Select Input Group #}
|
|
||||||
<div>
|
|
||||||
|
|
||||||
<select name="mark_as" id="update_status" class="form-select form-select-sm" style="min-width: 150px;">
|
|
||||||
<option selected>
|
|
||||||
----------
|
|
||||||
</option>
|
|
||||||
<option value="Offer">
|
|
||||||
{% trans "Offer Stage" %}
|
|
||||||
</option>
|
|
||||||
{# Include other options here, such as Interview, Offer, Rejected, etc. #}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Button #}
|
<!-- Main Card -->
|
||||||
<button type="submit" class="btn btn-main-action btn-sm">
|
<div class="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
|
||||||
<i class="fas fa-arrow-right me-1"></i> {% trans "Change Stage" %}
|
<!-- Table -->
|
||||||
</button>
|
<div class="overflow-x-auto">
|
||||||
{# email button#}
|
|
||||||
<button type="button" class="btn btn-outline-primary btn-sm"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
hx-boost='true'
|
|
||||||
data-bs-target="#emailModal"
|
|
||||||
hx-get="{% url 'compose_application_email' job.slug %}"
|
|
||||||
hx-target="#emailModalBody"
|
|
||||||
hx-include="#application-form"
|
|
||||||
title="Email Participants">
|
|
||||||
<i class="fas fa-envelope"></i>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endif %} {% endcomment %}
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<form id="application-form" action="{% url 'application_update_status' job.slug %}" method="get">
|
<form id="application-form" action="{% url 'application_update_status' job.slug %}" method="get">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table class="table application-table align-middle">
|
<table class="w-full border-collapse">
|
||||||
<thead>
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
{% comment %} <th style="width: 2%">
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
||||||
{% if applications %}
|
<i data-lucide="user" class="w-3 h-3 inline mr-1"></i> {% trans "Name" %}
|
||||||
<div class="form-check">
|
</th>
|
||||||
<input
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
||||||
type="checkbox" class="form-check-input" id="selectAllCheckbox">
|
<i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {% trans "Contact Info" %}
|
||||||
</div>
|
</th>
|
||||||
{% endif %}
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
||||||
</th> {% endcomment %}
|
<i data-lucide="briefcase" class="w-3 h-3 inline mr-1"></i> {% trans "Applied Position" %}
|
||||||
<th style="width: 15%"><i class="fas fa-user me-1"></i> {% trans "Name" %}</th>
|
</th>
|
||||||
<th style="width: 15%"><i class="fas fa-phone me-1"></i> {% trans "Contact" %}</th>
|
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
||||||
<th style="width: 15%"><i class="fas fa-briefcase me-1"></i> {% trans "Applied Position" %}</th>
|
<i data-lucide="calendar-check" class="w-3 h-3 inline mr-1"></i> {% trans "Hired Date" %}
|
||||||
<th class="text-center" style="width: 15%"><i class="fas fa-calendar-check me-1"></i> {% trans "Hired Date" %}</th>
|
</th>
|
||||||
<th class="text-center" style="width: 15%"><i class="fas fa-calendar-check me-1"></i> {% trans "Status" %}</th>
|
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[15%]">
|
||||||
<th style="width: 15%"><i class="fas fa-cog me-1"></i> {% trans "Actions" %}</th>
|
<i data-lucide="check-circle" class="w-3 h-3 inline mr-1"></i> {% trans "Status" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-center text-xs font-semibold text-temple-dark border-b-2 border-temple-red w-[10%]">
|
||||||
|
<i data-lucide="settings" class="w-3 h-3 inline mr-1"></i> {% trans "Actions" %}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for application in applications %}
|
{% for application in applications %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
{% comment %} <td>
|
<td class="px-4 py-3 border-b border-gray-200">
|
||||||
<div class="form-check">
|
<div class="font-semibold text-temple-dark">
|
||||||
<input name="candidate_ids" value="{{ application.id }}" type="checkbox" class="form-check-input rowCheckbox" id="application-{{ application.id }}">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</td> {% endcomment %}
|
|
||||||
<td>
|
|
||||||
<div class="application-name">
|
|
||||||
{{ application.name }}
|
{{ application.name }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3 border-b border-gray-200">
|
||||||
<div class="application-details">
|
<div class="text-xs text-gray-500">
|
||||||
<i class="fas fa-envelope me-1"></i> {{ application.email }}<br>
|
<i data-lucide="mail" class="w-3 h-3 inline mr-1"></i> {{ application.email }}<br>
|
||||||
<i class="fas fa-phone me-1"></i> {{ application.phone }}
|
<i data-lucide="phone" class="w-3 h-3 inline mr-1"></i> {{ application.phone }}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3 border-b border-gray-200">
|
||||||
<div class="application-details">
|
<div class="text-xs text-gray-500">
|
||||||
<strong>{{ job.title }}</strong><br>
|
<strong>{{ job.title }}</strong><br>
|
||||||
<small class="text-muted">{{ job.department }}</small>
|
<small>{{ job.department }}</small>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="px-4 py-3 text-center border-b border-gray-200">
|
||||||
<div class="hired-date">
|
<div class="text-xs text-gray-500">
|
||||||
{% if application.offer_date %}
|
{% if application.offer_date %}
|
||||||
<i class="fas fa-calendar me-1"></i>
|
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
||||||
{{ application.offer_date|date:"M d, Y" }}
|
{{ application.offer_date|date:"M d, Y" }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted">--</span>
|
<span class="text-gray-400">--</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">
|
<td class="px-4 py-3 text-center border-b border-gray-200">
|
||||||
<div class="hired-badge mt-1">
|
<div class="px-3 py-1.5 bg-green-500 text-white rounded-lg text-xs font-bold inline-flex items-center gap-1">
|
||||||
<i class="fas fa-check-circle"></i>
|
<i data-lucide="check-circle" class="w-3 h-3"></i>
|
||||||
{% trans "Hired" %}
|
{% trans "Hired" %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-4 py-3 border-b border-gray-200 text-center">
|
||||||
<div class="btn-group" role="group">
|
<div class="flex items-center justify-center gap-1">
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
<button type="button"
|
||||||
data-bs-toggle="modal"
|
class="px-3 py-1.5 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-xs font-medium"
|
||||||
data-bs-target="#candidateviewModal"
|
onclick="openCandidateModal('{% url 'application_criteria_view_htmx' application.pk %}')"
|
||||||
hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
|
title="{% trans 'View Profile' %}">
|
||||||
hx-target="#candidateviewModalBody"
|
<i data-lucide="eye" class="w-3 h-3"></i>
|
||||||
title="View Profile">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'application_resume_template' application.slug %}"
|
<a href="{% url 'application_resume_template' application.slug %}"
|
||||||
class="btn btn-outline-primary btn-sm"
|
class="px-3 py-1.5 border-2 border-temple-red text-temple-red rounded-lg hover:bg-temple-red hover:text-white transition text-xs font-medium"
|
||||||
title="View Resume Template">
|
title="{% trans 'View Resume Template' %}">
|
||||||
<i class="fas fa-file-alt"></i>
|
<i data-lucide="file-alt" class="w-3 h-3"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</form>
|
|
||||||
{% if not applications %}
|
{% if not applications %}
|
||||||
<div class="alert alert-info text-center mt-3 mb-0" role="alert">
|
<div class="p-8 text-center bg-blue-50 border border-blue-200 rounded-lg m-4">
|
||||||
<i class="fas fa-info-circle me-1"></i>
|
<i data-lucide="info" class="w-8 h-8 text-blue-500 mx-auto mb-2"></i>
|
||||||
{% trans "No applications have been hired for this position yet." %}
|
<p class="text-blue-700 text-sm">{% trans "No applications have been hired for this position yet." %}</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
<!-- Candidate View Modal -->
|
||||||
|
<div id="candidateviewModal" class="fixed inset-0 z-50 hidden" aria-labelledby="candidateviewModalLabel" role="dialog" aria-modal="true">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeCandidateModal()"></div>
|
||||||
|
|
||||||
<div class="modal fade modal-xl" id="candidateviewModal" tabindex="-1" aria-labelledby="candidateviewModalLabel" aria-hidden="true">
|
<!-- Modal Content -->
|
||||||
<div class="modal-dialog">
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
||||||
<div class="modal-content kaauh-card">
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
<h5 class="modal-title" id="candidateviewModalLabel" style="color: var(--kaauh-teal-dark);">
|
<h5 class="text-lg font-bold text-temple-dark" id="candidateviewModalLabel">
|
||||||
{% trans "Hired Application Details" %}
|
{% trans "Hired Application Details" %}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeCandidateModal()" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="candidateviewModalBody" class="modal-body">
|
<div id="candidateviewModalBody" class="flex-1 overflow-y-auto p-6">
|
||||||
<div class="text-center py-5 text-muted">
|
<div class="text-center py-10 text-gray-500">
|
||||||
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
{% trans "Loading content..." %}
|
<p>{% trans "Loading application data..." %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
|
||||||
|
<button type="button" class="w-full px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeCandidateModal()">
|
||||||
|
{% trans "Close" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email Modal -->
|
||||||
|
<div id="emailModal" class="fixed inset-0 z-50 hidden" aria-labelledby="emailModalLabel" role="dialog" aria-modal="true">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeEmailModal()"></div>
|
||||||
|
|
||||||
|
<!-- Modal Content -->
|
||||||
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||||
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-dark" id="emailModalLabel">
|
||||||
|
<i data-lucide="mail" class="w-5 h-5 inline mr-2"></i>{% trans "Compose Email" %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeEmailModal()" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="emailModalBody" class="flex-1 overflow-y-auto p-6">
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading email form..." %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -440,293 +261,142 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sync Results Modal -->
|
<!-- Sync Results Modal -->
|
||||||
<div class="modal fade" id="syncResultsModal" tabindex="-1" aria-labelledby="syncResultsModalLabel" aria-hidden="true">
|
<div id="syncResultsModal" class="fixed inset-0 z-50 hidden" aria-labelledby="syncResultsModalLabel" role="dialog" aria-modal="true">
|
||||||
<div class="modal-dialog modal-lg">
|
<!-- Backdrop -->
|
||||||
<div class="modal-content kaauh-card">
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeSyncResultsModal()"></div>
|
||||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
|
||||||
<h5 class="modal-title" id="syncResultsModalLabel" style="color: var(--kaauh-teal-dark);">
|
<!-- Modal Content -->
|
||||||
<i class="fas fa-sync me-2"></i>{% trans "Sync Results" %}
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||||
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-dark" id="syncResultsModalLabel">
|
||||||
|
<i data-lucide="refresh-ccw" class="w-5 h-5 inline mr-2"></i>{% trans "Sync Results" %}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeSyncResultsModal()" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="syncResultsModalBody" class="modal-body">
|
<div id="syncResultsModalBody" class="flex-1 overflow-y-auto p-6">
|
||||||
<div class="text-center py-5 text-muted">
|
<div class="text-center py-10 text-gray-500">
|
||||||
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
{% trans "Syncing applications..." %}
|
<p>{% trans "Syncing applications..." %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="px-6 py-4 border-t border-gray-200 bg-gray-50">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
|
<button type="button" class="w-full px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeSyncResultsModal()">
|
||||||
|
{% trans "Close" %}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Email Modal -->
|
|
||||||
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
|
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
|
||||||
<div class="modal-content kaauh-card">
|
|
||||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
|
||||||
<h5 class="modal-title" id="emailModalLabel" style="color: var(--kaauh-teal-dark);">
|
|
||||||
<i class="fas fa-envelope me-2"></i>{% trans "Compose Email" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div id="emailModalBody" class="modal-body">
|
|
||||||
<div class="text-center py-5 text-muted">
|
|
||||||
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
|
|
||||||
{% trans "Loading email form..." %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
|
// Reinitialize Lucide icons after content loads
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
if (typeof lucide !== 'undefined') {
|
||||||
const rowCheckboxes = document.querySelectorAll('.rowCheckbox');
|
lucide.createIcons();
|
||||||
|
|
||||||
if (selectAllCheckbox) {
|
|
||||||
// Function to safely update the header checkbox state
|
|
||||||
function updateSelectAllState() {
|
|
||||||
const checkedCount = Array.from(rowCheckboxes).filter(cb => cb.checked).length;
|
|
||||||
const totalCount = rowCheckboxes.length;
|
|
||||||
|
|
||||||
if (checkedCount === 0) {
|
|
||||||
selectAllCheckbox.checked = false;
|
|
||||||
selectAllCheckbox.indeterminate = false;
|
|
||||||
} else if (checkedCount === totalCount) {
|
|
||||||
selectAllCheckbox.checked = true;
|
|
||||||
selectAllCheckbox.indeterminate = false;
|
|
||||||
} else {
|
|
||||||
selectAllCheckbox.checked = false;
|
|
||||||
selectAllCheckbox.indeterminate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Logic for the 'Select All' checkbox (Clicking it updates all rows)
|
|
||||||
selectAllCheckbox.addEventListener('change', function () {
|
|
||||||
const isChecked = selectAllCheckbox.checked;
|
|
||||||
|
|
||||||
rowCheckboxes.forEach(checkbox => checkbox.removeEventListener('change', updateSelectAllState));
|
|
||||||
|
|
||||||
rowCheckboxes.forEach(function (checkbox) {
|
|
||||||
checkbox.checked = isChecked;
|
|
||||||
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
|
|
||||||
});
|
|
||||||
|
|
||||||
rowCheckboxes.forEach(checkbox => checkbox.addEventListener('change', updateSelectAllState));
|
|
||||||
updateSelectAllState();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Logic to update 'Select All' state based on row checkboxes
|
|
||||||
rowCheckboxes.forEach(function (checkbox) {
|
|
||||||
checkbox.addEventListener('change', updateSelectAllState);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initial check to set the correct state on load (in case items are pre-checked)
|
|
||||||
updateSelectAllState();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function syncHiredCandidates() {
|
// ========================================
|
||||||
const syncButton = document.querySelector('[onclick="syncHiredCandidates()"]');
|
// Modal Functions
|
||||||
const modal = new bootstrap.Modal(document.getElementById('syncResultsModal'));
|
// ========================================
|
||||||
|
|
||||||
// Show modal with loading state
|
function openCandidateModal(url) {
|
||||||
document.getElementById('syncResultsModalBody').innerHTML = `
|
const modal = document.getElementById('candidateviewModal');
|
||||||
<div class="text-center py-5">
|
const modalBody = document.getElementById('candidateviewModalBody');
|
||||||
<div class="spinner-border text-primary mb-3" role="status">
|
|
||||||
<span class="visually-hidden">Loading...</span>
|
// Reset content
|
||||||
</div>
|
modalBody.innerHTML = `
|
||||||
<h5>{% trans "Syncing hired applications..." %}</h5>
|
<div class="text-center py-10 text-gray-500">
|
||||||
<p class="text-muted">{% trans "Please wait while we sync applications to external sources." %}</p>
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading application data..." %}</p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
modal.show();
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
// Disable sync button during sync
|
// Load content via HTMX
|
||||||
syncButton.disabled = true;
|
if (url && typeof htmx !== 'undefined') {
|
||||||
syncButton.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i> {% trans "Syncing..." %}';
|
htmx.ajax('GET', url, {target: '#candidateviewModalBody', swap: 'innerHTML'});
|
||||||
|
|
||||||
// Perform sync request
|
|
||||||
fetch(`{% url 'sync_hired_applications' job.slug %}`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRFToken': getCookie('csrftoken')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status === 'queued') {
|
|
||||||
// Task is queued, start polling for status
|
|
||||||
console.log('Sync task queued with ID:', data.task_id);
|
|
||||||
pollSyncStatus(data.task_id);
|
|
||||||
} else if (data.status === 'success') {
|
|
||||||
displaySyncResults(data.results);
|
|
||||||
} else {
|
|
||||||
displaySyncError(data.message);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Sync error:', error);
|
|
||||||
displaySyncError('{% trans "An unexpected error occurred during sync." %}');
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
// Re-enable sync button
|
|
||||||
syncButton.disabled = false;
|
|
||||||
syncButton.innerHTML = '<i class="fas fa-sync me-1"></i> {% trans "Sync to Sources" %}';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function displaySyncResults(results) {
|
// Reinitialize icons
|
||||||
const modalBody = document.getElementById('syncResultsModalBody');
|
|
||||||
console.log('Sync results:', results);
|
|
||||||
let html = '<div class="sync-results">';
|
|
||||||
|
|
||||||
// Summary section
|
|
||||||
html += `
|
|
||||||
<div class="alert alert-info mb-4">
|
|
||||||
<h6 class="alert-heading">{% trans "Sync Summary" %}</h6>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-3">
|
|
||||||
<strong>{% trans "Total Sources:" %}</strong> ${results.source_results?.total_sources}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<strong>{% trans "Successful:" %}</strong> <span class="text-success">${results.successful_syncs}</span>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<strong>{% trans "Failed:" %}</strong> <span class="text-danger">${results.failed_syncs}</span>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<strong>{% trans "Applications Synced:" %}</strong> ${results.total_candidates}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Detailed results for each source
|
|
||||||
if (results.sources && results.sources.length > 0) {
|
|
||||||
html += '<h6 class="mb-3">{% trans "Source Details" %}</h6>';
|
|
||||||
|
|
||||||
results.sources.forEach(source => {
|
|
||||||
const statusClass = source.status === 'success' ? 'success' : 'danger';
|
|
||||||
const statusIcon = source.status === 'success' ? 'check-circle' : 'exclamation-triangle';
|
|
||||||
|
|
||||||
html += `
|
|
||||||
<div class="card mb-3">
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<strong>${source.source_name}</strong>
|
|
||||||
<span class="badge bg-${statusClass}">
|
|
||||||
<i class="fas fa-${statusIcon} me-1"></i>
|
|
||||||
${source.status.toUpperCase()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<small class="text-muted">{% trans "Applications Processed:" %}</small>
|
|
||||||
<div class="fw-bold">${source.candidates_processed}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
<small class="text-muted">{% trans "Duration:" %}</small>
|
|
||||||
<div class="fw-bold">${source.duration}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
${source.message ? `<div class="mt-2"><small class="text-muted">{% trans "Message:" %}</small><div>${source.message}</div></div>` : ''}
|
|
||||||
${source.error ? `<div class="mt-2 text-danger"><small>{% trans "Error:" %}</small><div>${source.error}</div></div>` : ''}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
html += '</div>';
|
|
||||||
modalBody.innerHTML = html;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pollSyncStatus(taskId) {
|
|
||||||
console.log('Polling for sync status...');
|
|
||||||
const pollInterval = setInterval(() => {
|
|
||||||
fetch(`/sync/task/${taskId}/status/`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRFToken': getCookie('csrftoken')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
if (data.status === 'completed') {
|
|
||||||
clearInterval(pollInterval);
|
|
||||||
displaySyncResults(data.result);
|
|
||||||
} else if (data.status === 'failed') {
|
|
||||||
clearInterval(pollInterval);
|
|
||||||
displaySyncError(data.message || '{% trans "Sync task failed" %}');
|
|
||||||
} else if (data.status === 'running') {
|
|
||||||
updateSyncProgress(data.message);
|
|
||||||
}
|
|
||||||
// For 'pending' status, continue polling
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Polling error:', error);
|
|
||||||
clearInterval(pollInterval);
|
|
||||||
displaySyncError('{% trans "Failed to check sync status" %}');
|
|
||||||
});
|
|
||||||
}, 2000); // Poll every 2 seconds
|
|
||||||
|
|
||||||
// Set a timeout to stop polling after 5 minutes
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
clearInterval(pollInterval);
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
displaySyncError('{% trans "Sync timed out after 5 minutes" %}');
|
}, 100);
|
||||||
}, 300000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSyncProgress(message) {
|
function closeCandidateModal() {
|
||||||
const modalBody = document.getElementById('syncResultsModalBody');
|
const modal = document.getElementById('candidateviewModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEmailModal() {
|
||||||
|
const modal = document.getElementById('emailModal');
|
||||||
|
const modalBody = document.getElementById('emailModalBody');
|
||||||
|
const applicationForm = document.getElementById('application-form');
|
||||||
|
const url = '{% url "compose_application_email" job.slug %}';
|
||||||
|
|
||||||
|
// Reset content
|
||||||
modalBody.innerHTML = `
|
modalBody.innerHTML = `
|
||||||
<div class="text-center py-5">
|
<div class="text-center py-10 text-gray-500">
|
||||||
<div class="spinner-border text-primary mb-3" role="status">
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
<span class="visually-hidden">Loading...</span>
|
<p>{% trans "Loading email form..." %}</p>
|
||||||
</div>
|
|
||||||
<h5>{% trans "Sync in progress..." %}</h5>
|
|
||||||
<p class="text-muted">${message}</p>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Load content via HTMX with form data
|
||||||
|
if (url && typeof htmx !== 'undefined' && applicationForm) {
|
||||||
|
htmx.ajax('GET', url, {
|
||||||
|
target: '#emailModalBody',
|
||||||
|
swap: 'innerHTML',
|
||||||
|
values: htmx.values(applicationForm)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function displaySyncError(message) {
|
// Reinitialize icons
|
||||||
const modalBody = document.getElementById('syncResultsModalBody');
|
setTimeout(() => {
|
||||||
modalBody.innerHTML = `
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
<div class="alert alert-danger text-center">
|
}, 100);
|
||||||
<i class="fas fa-exclamation-triangle fa-3x mb-3"></i>
|
|
||||||
<h5>{% trans "Sync Failed" %}</h5>
|
|
||||||
<p>${message}</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get CSRF token
|
function closeEmailModal() {
|
||||||
function getCookie(name) {
|
const modal = document.getElementById('emailModal');
|
||||||
let cookieValue = null;
|
modal.classList.add('hidden');
|
||||||
if (document.cookie && document.cookie !== '') {
|
document.body.style.overflow = '';
|
||||||
const cookies = document.cookie.split(';');
|
|
||||||
for (let i = 0; i < cookies.length; i++) {
|
|
||||||
const cookie = cookies[i].trim();
|
|
||||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
|
||||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openSyncResultsModal() {
|
||||||
|
const modal = document.getElementById('syncResultsModal');
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeSyncResultsModal() {
|
||||||
|
const modal = document.getElementById('syncResultsModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
}
|
}
|
||||||
return cookieValue;
|
|
||||||
|
// Close modals on escape key
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
closeCandidateModal();
|
||||||
|
closeEmailModal();
|
||||||
|
closeSyncResultsModal();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -3,236 +3,72 @@
|
|||||||
|
|
||||||
{% block title %}{% trans "Delete Applicant" %} - {{ block.super }}{% endblock %}
|
{% block title %}{% trans "Delete Applicant" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
|
||||||
<style>
|
|
||||||
/* KAAT-S UI Variables */
|
|
||||||
:root {
|
|
||||||
--kaauh-teal: #00636e;
|
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-primary-text: #343a40;
|
|
||||||
--kaauh-danger: #dc3545;
|
|
||||||
--kaauh-warning: #ffc107;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Container & Card Styling */
|
|
||||||
.kaauh-card {
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
background-color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Warning Section */
|
|
||||||
.warning-section {
|
|
||||||
background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%);
|
|
||||||
border: 1px solid #ffeeba;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 2rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-icon {
|
|
||||||
font-size: 4rem;
|
|
||||||
color: var(--kaauh-warning);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-title {
|
|
||||||
color: #856404;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-text {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Candidate Info Card */
|
|
||||||
.candidate-info {
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-bottom: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-icon {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin-right: 1rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-content {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--kaauh-primary-text);
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
color: #6c757d;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button Styling */
|
|
||||||
.btn-danger {
|
|
||||||
background-color: var(--kaauh-danger);
|
|
||||||
border-color: var(--kaauh-danger);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.btn-danger:hover {
|
|
||||||
background-color: #c82333;
|
|
||||||
border-color: #bd2130;
|
|
||||||
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background-color: #6c757d;
|
|
||||||
border-color: #6c757d;
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Consequence List */
|
|
||||||
.consequence-list {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consequence-list li {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
border-bottom: 1px solid #e9ecef;
|
|
||||||
color: #6c757d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consequence-list li:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.consequence-list li i {
|
|
||||||
color: var(--kaauh-danger);
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Candidate Profile Image */
|
|
||||||
.candidate-avatar {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 3px solid var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-placeholder {
|
|
||||||
width: 80px;
|
|
||||||
height: 80px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #e9ecef;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border: 3px solid var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<!-- Header Section -->
|
<!-- Header -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 class="text-3xl font-bold text-temple-red mb-2 flex items-center gap-3">
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="alert-triangle" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
{% trans "Delete Applicant" %}
|
{% trans "Delete Applicant" %}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">
|
<p class="text-gray-600">
|
||||||
{% trans "You are about to delete an applicant's application. This action cannot be undone." %}
|
{% trans "You are about to delete an applicant's application. This action cannot be undone." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'candidate_detail' object.slug %}" class="btn btn-secondary">
|
<a href="{% url 'candidate_detail' object.slug %}" class="inline-flex items-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-6 py-2.5 rounded-xl transition">
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Applicant" %}
|
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Applicant" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row justify-content-center">
|
<div class="flex justify-center">
|
||||||
<div class="col-lg-8">
|
<div class="w-full max-w-4xl">
|
||||||
<!-- Warning Section -->
|
<!-- Warning Section -->
|
||||||
<div class="warning-section">
|
<div class="bg-gradient-to-br from-yellow-100 to-amber-50 border border-yellow-200 rounded-2xl p-8 mb-6 text-center">
|
||||||
<div class="warning-icon">
|
<div class="bg-temple-red/10 w-20 h-20 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
<i data-lucide="alert-triangle" class="w-12 h-12 text-temple-red"></i>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3>
|
<h3 class="text-2xl font-bold text-amber-800 mb-3">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||||
<p class="warning-text">
|
<p class="text-amber-700">
|
||||||
{% trans "Deleting this applicant will permanently remove all associated data. Please review the information below carefully before proceeding." %}
|
{% trans "Deleting this applicant will permanently remove all associated data. Please review information below carefully before proceeding." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Candidate Information -->
|
<!-- Applicant Info Card -->
|
||||||
<div class="card kaauh-card mb-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||||
<div class="card-header bg-white border-bottom">
|
<div class="bg-white border-b border-gray-200 p-5">
|
||||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
<h5 class="text-lg font-bold text-temple-red flex items-center gap-2">
|
||||||
<i class="fas fa-user me-2"></i>
|
<i data-lucide="user" class="w-5 h-5"></i>
|
||||||
{% trans "Applicant to be Deleted" %}
|
{% trans "Applicant to be Deleted" %}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<div class="candidate-info">
|
<div class="bg-gray-50 rounded-xl p-6 mb-6">
|
||||||
<div class="d-flex align-items-center mb-4">
|
<div class="flex items-center gap-4 mb-5 pb-5 border-b border-gray-200">
|
||||||
{% if object.profile_image %}
|
{% if object.profile_image %}
|
||||||
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}" class="candidate-avatar me-3">
|
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}" class="w-20 h-20 rounded-full object-cover border-3 border-temple-red">
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="avatar-placeholder me-3">
|
<div class="w-20 h-20 rounded-full bg-gray-200 flex items-center justify-center border-3 border-temple-red">
|
||||||
<i class="fas fa-user text-muted fa-2x"></i>
|
<i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div>
|
<div>
|
||||||
<h4 class="mb-1">{{ object.name }}</h4>
|
<h4 class="text-xl font-bold text-gray-900 mb-1">{{ object.name }}</h4>
|
||||||
{% if object.email %}
|
{% if object.email %}
|
||||||
<p class="text-muted mb-0">{{ object.email }}</p>
|
<p class="text-gray-600">{{ object.email }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-item">
|
<div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
|
||||||
<div class="info-icon">
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||||
<i class="fas fa-briefcase"></i>
|
<i data-lucide="briefcase" class="w-5 h-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-content">
|
<div class="flex-1">
|
||||||
<div class="info-label">{% trans "Position Applied" %}</div>
|
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Position Applied" %}</div>
|
||||||
<div class="info-value">
|
<div class="text-gray-900 text-base">
|
||||||
{% if object.job_posting %}
|
{% if object.job_posting %}
|
||||||
{{ object.job_posting.title }}
|
{{ object.job_posting.title }}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -243,36 +79,38 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if object.phone %}
|
{% if object.phone %}
|
||||||
<div class="info-item">
|
<div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
|
||||||
<div class="info-icon">
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||||
<i class="fas fa-phone"></i>
|
<i data-lucide="phone" class="w-5 h-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-content">
|
<div class="flex-1">
|
||||||
<div class="info-label">{% trans "Phone" %}</div>
|
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Phone" %}</div>
|
||||||
<div class="info-value">{{ object.phone }}</div>
|
<div class="text-gray-900 text-base">{{ object.phone }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="info-item">
|
<div class="flex items-start gap-4 mb-5 pb-5 border-b border-gray-200 last:border-0 last:pb-0 last:mb-0">
|
||||||
<div class="info-icon">
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||||
<i class="fas fa-calendar"></i>
|
<i data-lucide="calendar" class="w-5 h-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-content">
|
<div class="flex-1">
|
||||||
<div class="info-label">{% trans "Applied On" %}</div>
|
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Applied On" %}</div>
|
||||||
<div class="info-value">{{ object.created_at|date:"F d, Y" }}</div>
|
<div class="text-gray-900 text-base">{{ object.created_at|date:"F d, Y" }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-item">
|
<div class="flex items-start gap-4">
|
||||||
<div class="info-icon">
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center shrink-0">
|
||||||
<i class="fas fa-info-circle"></i>
|
<i data-lucide="info" class="w-5 h-5"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-content">
|
<div class="flex-1">
|
||||||
<div class="info-label">{% trans "Status" %}</div>
|
<div class="text-xs font-semibold text-gray-700 uppercase tracking-wider mb-1">{% trans "Status" %}</div>
|
||||||
<div class="info-value">
|
<div class="text-gray-900 text-base">
|
||||||
{% if object.status %}
|
{% if object.status %}
|
||||||
<span class="badge bg-info">{{ object.get_status_display }}</span>
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||||||
|
{{ object.get_status_display }}
|
||||||
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Not specified" %}
|
{% trans "Not specified" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -283,34 +121,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Consequences -->
|
<!-- Consequences Card -->
|
||||||
<div class="card kaauh-card mb-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
||||||
<div class="card-header bg-white border-bottom">
|
<div class="bg-white border-b border-gray-200 p-5">
|
||||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
<h5 class="text-lg font-bold text-temple-red flex items-center gap-2">
|
||||||
<i class="fas fa-list me-2"></i>
|
<i data-lucide="list" class="w-5 h-5"></i>
|
||||||
{% trans "What will happen when you delete this applicant?" %}
|
{% trans "What will happen when you delete this applicant?" %}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<ul class="consequence-list">
|
<ul class="space-y-3">
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "The applicant profile and all personal information will be permanently deleted" %}
|
{% trans "The applicant profile and all personal information will be permanently deleted" %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "All application data and documents will be removed" %}
|
{% trans "All application data and documents will be removed" %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "Interview schedules and history will be deleted" %}
|
{% trans "Interview schedules and history will be deleted" %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700 pb-3 border-b border-gray-200 last:border-0 last:pb-0">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "Any associated notes and communications will be lost" %}
|
{% trans "Any associated notes and communications will be lost" %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li class="flex items-start gap-2 text-gray-700">
|
||||||
<i class="fas fa-times-circle"></i>
|
<i data-lucide="x-circle" class="w-5 h-5 text-red-500 shrink-0 mt-0.5"></i>
|
||||||
{% trans "This action cannot be undone under any circumstances" %}
|
{% trans "This action cannot be undone under any circumstances" %}
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -318,30 +156,27 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Confirmation Form -->
|
<!-- Confirmation Form -->
|
||||||
<div class="card kaauh-card">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<form method="post" id="deleteForm">
|
<form method="post" id="deleteForm">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="mb-4">
|
<div class="mb-6">
|
||||||
<div class="form-check">
|
<label class="flex items-start gap-3 cursor-pointer">
|
||||||
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required>
|
<input type="checkbox" id="confirm_delete" name="confirm_delete" required class="w-5 h-5 mt-0.5 rounded border-gray-300 text-temple-red focus:ring-temple-red focus:ring-offset-0">
|
||||||
<label class="form-check-label" for="confirm_delete">
|
<span class="text-gray-900 font-medium">
|
||||||
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this applicant." %}</strong>
|
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this applicant." %}</strong>
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="flex flex-col sm:flex-row gap-3 justify-between">
|
||||||
<a href="{% url 'candidate_detail' object.slug %}" class="btn btn-secondary btn-lg">
|
<a href="{% url 'candidate_detail' object.slug %}" class="inline-flex items-center justify-center gap-2 bg-gray-600 hover:bg-gray-700 text-white font-semibold px-8 py-3 rounded-xl transition">
|
||||||
<i class="fas fa-times me-2"></i>
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
{% trans "Cancel" %}
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
<button type="submit"
|
<button type="submit" id="deleteButton" disabled class="inline-flex items-center justify-center gap-2 bg-red-500 hover:bg-red-600 text-white font-semibold px-8 py-3 rounded-xl transition shadow-sm hover:shadow-md disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
class="btn btn-danger btn-lg"
|
<i data-lucide="trash-2" class="w-5 h-5"></i>
|
||||||
id="deleteButton"
|
|
||||||
disabled>
|
|
||||||
<i class="fas fa-trash me-2"></i>
|
|
||||||
{% trans "Delete Applicant Permanently" %}
|
{% trans "Delete Applicant Permanently" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -361,26 +196,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
function validateForm() {
|
function validateForm() {
|
||||||
const checkboxChecked = confirmDeleteCheckbox.checked;
|
const checkboxChecked = confirmDeleteCheckbox.checked;
|
||||||
deleteButton.disabled = !checkboxChecked;
|
deleteButton.disabled = !checkboxChecked;
|
||||||
|
|
||||||
if (checkboxChecked) {
|
|
||||||
deleteButton.classList.remove('btn-secondary');
|
|
||||||
deleteButton.classList.add('btn-danger');
|
|
||||||
} else {
|
|
||||||
deleteButton.classList.remove('btn-danger');
|
|
||||||
deleteButton.classList.add('btn-secondary');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
confirmDeleteCheckbox.addEventListener('change', validateForm);
|
confirmDeleteCheckbox.addEventListener('change', validateForm);
|
||||||
|
validateForm();
|
||||||
|
|
||||||
// Add confirmation before final submission
|
lucide.createIcons();
|
||||||
/*deleteForm.addEventListener('submit', function(e) {
|
|
||||||
const confirmMessage = "{% trans 'Are you absolutely sure you want to delete this candidate? This action cannot be undone.' %}";
|
|
||||||
if (!confirm(confirmMessage)) {
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,33 +1,64 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %} {% trans "Interview Calendar" %} {% endblock %}
|
{% block title %} {% trans "Interview Calendar" %} {% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@5.10.1/main.min.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--calendar-color: #00636e;
|
--calendar-color: #9d2235;
|
||||||
--calendar-light: rgba(0, 99, 110, 0.1);
|
--calendar-light: rgba(157, 34, 53, 0.1);
|
||||||
--calendar-hover: rgba(0, 99, 110, 0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.calendar-container {
|
.calendar-container {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 0.75rem;
|
border-radius: 1rem;
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border: 1px solid var(--kaauh-border);
|
border: 1px solid #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-toolbar-title {
|
.fc-toolbar-title {
|
||||||
color: var(--calendar-color) !important;
|
color: #9d2235 !important;
|
||||||
font-weight: 700 !important;
|
font-weight: 700 !important;
|
||||||
|
font-size: 1.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-button-primary {
|
.fc-button-primary {
|
||||||
background-color: var(--calendar-color) !important;
|
background-color: #9d2235 !important;
|
||||||
border-color: var(--calendar-color) !important;
|
border-color: #9d2235 !important;
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
padding: 0.5rem 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-button-primary:hover {
|
||||||
|
background-color: #7a1a29 !important;
|
||||||
|
border-color: #7a1a29 !important;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(157, 34, 53, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-button-primary:not(:disabled):active,
|
||||||
|
.fc-button-primary.fc-button-active {
|
||||||
|
background-color: #5e1320 !important;
|
||||||
|
border-color: #5e1320 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-daygrid-day.fc-day-today {
|
||||||
|
background-color: rgba(157, 34, 53, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-daygrid-day-number {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-col-header-cell-cushion {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fc-event {
|
.fc-event {
|
||||||
@ -41,6 +72,7 @@
|
|||||||
padding: 0.25rem 0.6rem;
|
padding: 0.25rem 0.6rem;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.status-scheduled { background-color: #e3f2fd; color: #0d47a1; }
|
.status-scheduled { background-color: #e3f2fd; color: #0d47a1; }
|
||||||
@ -53,70 +85,83 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
padding: 1rem;
|
padding: 1.25rem;
|
||||||
background-color: #f9fbfd;
|
background-color: #f9fafb;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.75rem;
|
||||||
border: 1px dashed #dee2e6;
|
border: 1px solid #e5e7eb;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-item {
|
.legend-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.75rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.875rem;
|
||||||
color: #495057;
|
color: #4b5563;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-color {
|
.legend-color {
|
||||||
width: 12px;
|
width: 14px;
|
||||||
height: 12px;
|
height: 14px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<!-- Header -->
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1 page-header">{% trans "Interview Calendar" %}</h1>
|
<h1 class="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
|
||||||
<p class="text-muted mb-0">{{ job.title|default:"Global Schedule" }}</p>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="calendar" class="w-8 h-8 text-temple-red"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center">
|
{% trans "Interview Calendar" %}
|
||||||
<p class="text-muted small mb-0">Tenhal | تنحل</p>
|
</h1>
|
||||||
|
<p class="text-gray-600">{{ job.title|default:"Global Schedule" }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="calendar-container">
|
<!-- Calendar -->
|
||||||
|
<div class="calendar-container mb-6">
|
||||||
<div id="calendar"></div>
|
<div id="calendar"></div>
|
||||||
|
|
||||||
<div class="calendar-legend">
|
<div class="calendar-legend">
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<div class="legend-color" style="background-color: #00636e;"></div>
|
<div class="legend-color" style="background-color: #00636e; box-shadow: 0 0 8px rgba(0, 99, 110, 0.4);"></div>
|
||||||
<span>{% trans "Scheduled" %}</span>
|
<span>{% trans "Scheduled" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<div class="legend-color" style="background-color: #00a86b;"></div>
|
<div class="legend-color" style="background-color: #00a86b; box-shadow: 0 0 8px rgba(0, 168, 107, 0.4);"></div>
|
||||||
<span>{% trans "Confirmed" %}</span>
|
<span>{% trans "Confirmed" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<div class="legend-color" style="background-color: #e74c3c;"></div>
|
<div class="legend-color" style="background-color: #e74c3c; box-shadow: 0 0 8px rgba(231, 76, 60, 0.4);"></div>
|
||||||
<span>{% trans "Cancelled" %}</span>
|
<span>{% trans "Cancelled" %}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="legend-item">
|
<div class="legend-item">
|
||||||
<div class="legend-color" style="background-color: #95a5a6;"></div>
|
<div class="legend-color" style="background-color: #95a5a6; box-shadow: 0 0 8px rgba(149, 165, 166, 0.4);"></div>
|
||||||
<span>{% trans "Completed" %}</span>
|
<span>{% trans "Completed" %}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="interview-details mt-4" id="interview-details" style="display: none;">
|
<!-- Interview Details -->
|
||||||
<div class="card shadow-sm border-start border-4 border-info">
|
<div class="interview-details hidden" id="interview-details">
|
||||||
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<h5 class="mb-0 text-primary-theme"><i class="fas fa-info-circle me-2"></i>{% trans "Interview Details" %}</h5>
|
<div class="p-6 border-b border-gray-200 flex justify-between items-center bg-white">
|
||||||
<button type="button" class="btn-close" id="close-details"></button>
|
<h5 class="text-xl font-bold text-temple-red flex items-center gap-2">
|
||||||
|
<i data-lucide="info" class="w-5 h-5"></i>
|
||||||
|
{% trans "Interview Details" %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" id="close-details" class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" id="interview-info">
|
<div class="p-6" id="interview-info">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -129,7 +174,6 @@
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
var calendarEl = document.getElementById('calendar');
|
var calendarEl = document.getElementById('calendar');
|
||||||
|
|
||||||
// FIX: Parse the JSON script to avoid "None is not defined" error
|
|
||||||
const eventsData = JSON.parse(document.getElementById('calendar-events-data').textContent);
|
const eventsData = JSON.parse(document.getElementById('calendar-events-data').textContent);
|
||||||
|
|
||||||
var calendar = new FullCalendar.Calendar(calendarEl, {
|
var calendar = new FullCalendar.Calendar(calendarEl, {
|
||||||
@ -160,45 +204,49 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const statusText = status.charAt(0).toUpperCase() + status.slice(1);
|
const statusText = status.charAt(0).toUpperCase() + status.slice(1);
|
||||||
|
|
||||||
let meetingInfo = '';
|
let meetingInfo = '';
|
||||||
// FIX: Corrected translation tags and property checks
|
|
||||||
if (event.extendedProps.meeting_id) {
|
if (event.extendedProps.meeting_id) {
|
||||||
meetingInfo = `
|
meetingInfo = `
|
||||||
<div class="mt-3 p-3 bg-light rounded border">
|
<div class="mt-3 p-3 bg-gray-50 rounded-lg border border-gray-200">
|
||||||
<h6 class="text-primary-theme"><i class="fas fa-video me-2"></i>{% trans "Meeting Information" %}</h6>
|
<h6 class="text-temple-red font-bold mb-2 flex items-center gap-2">
|
||||||
<p class="mb-1"><strong>{% trans "Meeting ID:" %}</strong> ${event.extendedProps.meeting_id}</p>
|
<i data-lucide="video" class="w-4 h-4"></i>
|
||||||
<p class="mb-0"><strong>{% trans "Join URL:" %}</strong> <a href="${event.extendedProps.join_url}" target="_blank" class="text-break">${event.extendedProps.join_url}</a></p>
|
{% trans "Meeting Information" %}
|
||||||
|
</h6>
|
||||||
|
<p class="mb-1 text-sm"><strong>{% trans "Meeting ID:" %}</strong> ${event.extendedProps.meeting_id}</p>
|
||||||
|
<p class="mb-0 text-sm"><strong>{% trans "Join URL:" %}</strong> <a href="${event.extendedProps.join_url}" target="_blank" class="text-temple-red hover:underline break-all">${event.extendedProps.join_url}</a></p>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
infoContainer.innerHTML = `
|
infoContainer.innerHTML = `
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="col-md-6 border-end">
|
<div class="border-r border-gray-200 pr-0 md:pr-6">
|
||||||
<h6 class="text-muted text-uppercase small fw-bold">{% trans "Candidate" %}</h6>
|
<h6 class="text-gray-500 text-xs font-bold uppercase mb-3">{% trans "Candidate" %}</h6>
|
||||||
<p class="mb-1"><strong>{% trans "Name:" %}</strong> ${event.extendedProps.candidate}</p>
|
<p class="mb-2 text-sm"><strong>{% trans "Name:" %}</strong> ${event.extendedProps.candidate}</p>
|
||||||
<p><strong>{% trans "Email:" %}</strong> ${event.extendedProps.email}</p>
|
<p class="text-sm"><strong>{% trans "Email:" %}</strong> ${event.extendedProps.email}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<h6 class="text-muted text-uppercase small fw-bold">{% trans "Schedule" %}</h6>
|
<h6 class="text-gray-500 text-xs font-bold uppercase mb-3">{% trans "Schedule" %}</h6>
|
||||||
<p class="mb-1"><strong>{% trans "Date:" %}</strong> ${event.start.toLocaleDateString()}</p>
|
<p class="mb-2 text-sm"><strong>{% trans "Date:" %}</strong> ${event.start.toLocaleDateString()}</p>
|
||||||
<p class="mb-1"><strong>{% trans "Time:" %}</strong> ${event.start.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</p>
|
<p class="mb-2 text-sm"><strong>{% trans "Time:" %}</strong> ${event.start.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</p>
|
||||||
<p><strong>{% trans "Status:" %}</strong> <span class="status-badge ${statusClass}">${statusText}</span></p>
|
<p class="text-sm"><strong>{% trans "Status:" %}</strong> <span class="status-badge ${statusClass}">${statusText}</span></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${meetingInfo}
|
${meetingInfo}
|
||||||
<div class="mt-4 pt-3 border-top">
|
<div class="mt-4 pt-3 border-t border-gray-200">
|
||||||
<a href="${event.url}" class="btn btn-main-action btn-sm">
|
<a href="${event.url}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-external-link-alt me-1"></i> {% trans "View Full Application" %}
|
<i data-lucide="external-link" class="w-4 h-4"></i>
|
||||||
|
{% trans "View Full Application" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
detailsContainer.style.display = 'block';
|
detailsContainer.classList.remove('hidden');
|
||||||
detailsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
detailsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('close-details').addEventListener('click', function() {
|
document.getElementById('close-details').addEventListener('click', function() {
|
||||||
document.getElementById('interview-details').style.display = 'none';
|
document.getElementById('interview-details').classList.add('hidden');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,144 +1,130 @@
|
|||||||
<!-- templates/recruitment/interview_detail.html -->
|
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block title %}{% trans "Interview Details" %} - {{ block.super }}{% endblock %}
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--calendar-color: #00636e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-header {
|
|
||||||
background-color: var(--calendar-color);
|
|
||||||
color: white;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-card {
|
|
||||||
border-left: 4px solid var(--calendar-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-badge {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-scheduled {
|
|
||||||
background-color: #e3f2fd;
|
|
||||||
color: #0d47a1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-confirmed {
|
|
||||||
background-color: #e8f5e9;
|
|
||||||
color: #1b5e20;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-cancelled {
|
|
||||||
background-color: #ffebee;
|
|
||||||
color: #b71c1c;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-completed {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
color: #424242;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container mt-4">
|
<div class="px-4 py-6">
|
||||||
<div class="detail-header">
|
<!-- Header -->
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
<div class="bg-gradient-to-br from-temple-red to-red-800 rounded-xl shadow-xl p-6 mb-6 text-white">
|
||||||
<h1 class="h3 mb-0">{% trans "Interview Details" %}</h1>
|
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||||
<div>
|
<div class="flex-1">
|
||||||
<span class="h5">{{ job.title }}</span>
|
<h1 class="text-3xl font-bold mb-2 flex items-center gap-2">
|
||||||
|
<i data-lucide="calendar-check" class="w-8 h-8"></i>
|
||||||
|
{% trans "Interview Details" %}
|
||||||
|
</h1>
|
||||||
|
<p class="text-red-100 text-lg">{{ job.title }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card detail-card mb-4">
|
<!-- Details Card -->
|
||||||
<div class="card-body">
|
<div class="bg-white rounded-xl shadow-md border-l-4 border-temple-red overflow-hidden mb-6">
|
||||||
<div class="row">
|
<div class="p-6">
|
||||||
<div class="col-md-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
<h5>{% trans "Applicant Information"%}</h5>
|
<!-- Applicant Information -->
|
||||||
<table class="table table-borderless">
|
<div class="bg-gray-50 rounded-lg p-5">
|
||||||
<tr>
|
<h3 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||||
<td><strong>{% trans "Name:" %}</strong></td>
|
<i data-lucide="user" class="w-5 h-5"></i>
|
||||||
<td>{{ interview.candidate.name }}</td>
|
{% trans "Applicant Information" %}
|
||||||
</tr>
|
</h3>
|
||||||
<tr>
|
<div class="space-y-3">
|
||||||
<td><strong>{% trans "Email:" %}</strong></td>
|
<div class="flex">
|
||||||
<td>{{ interview.candidate.email }}</td>
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Name:" %}</span>
|
||||||
</tr>
|
<span class="text-gray-900">{{ interview.candidate.name }}</span>
|
||||||
<tr>
|
|
||||||
<td><strong>{% trans "Phone:" %}</strong></td>
|
|
||||||
<td>{{ interview.candidate.phone|default:"Not provided" }}</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="flex">
|
||||||
<h5>{% trans "Interview Details" %}</h5>
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Email:" %}</span>
|
||||||
<table class="table table-borderless">
|
<span class="text-gray-900">{{ interview.candidate.email }}</span>
|
||||||
<tr>
|
</div>
|
||||||
<td><strong>{% trans "Date:" %}</strong></td>
|
<div class="flex">
|
||||||
<td>{{ interview.interview_date|date:"l, F j, Y" }}</td>
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Phone:" %}</span>
|
||||||
</tr>
|
<span class="text-gray-900">{{ interview.candidate.phone|default:"{% trans 'Not provided' %}" }}</span>
|
||||||
<tr>
|
</div>
|
||||||
<td><strong>{% trans "Time:" %}</strong></td>
|
</div>
|
||||||
<td>{{ interview.interview_time|time:"g:i A" }}</td>
|
</div>
|
||||||
</tr>
|
|
||||||
<tr>
|
<!-- Interview Details -->
|
||||||
<td><strong>{% trans "Status:" %}</strong></td>
|
<div class="bg-gray-50 rounded-lg p-5">
|
||||||
<td>
|
<h3 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||||
<span class="status-badge status-{{ interview.status }}">
|
<i data-lucide="clock" class="w-5 h-5"></i>
|
||||||
|
{% trans "Interview Details" %}
|
||||||
|
</h3>
|
||||||
|
<div class="space-y-3">
|
||||||
|
<div class="flex">
|
||||||
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Date:" %}</span>
|
||||||
|
<span class="text-gray-900">{{ interview.interview_date|date:"l, F j, Y" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Time:" %}</span>
|
||||||
|
<span class="text-gray-900">{{ interview.interview_time|time:"g:i A" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Status:" %}</span>
|
||||||
|
<span class="status-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium
|
||||||
|
{% if interview.status == 'scheduled' %}bg-blue-100 text-blue-800
|
||||||
|
{% elif interview.status == 'confirmed' %}bg-green-100 text-green-800
|
||||||
|
{% elif interview.status == 'cancelled' %}bg-red-100 text-red-800
|
||||||
|
{% else %}bg-gray-100 text-gray-800{% endif %}">
|
||||||
{{ interview.status|title }}
|
{{ interview.status|title }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Meeting Information (if Zoom meeting exists) -->
|
||||||
{% if interview.zoom_meeting %}
|
{% if interview.zoom_meeting %}
|
||||||
<div class="mt-4">
|
<div class="mt-6 bg-gray-50 rounded-lg p-5">
|
||||||
<h5>{% trans "Meeting Information" %}</h5>
|
<h3 class="text-lg font-semibold text-temple-dark mb-4 flex items-center gap-2">
|
||||||
<table class="table table-borderless">
|
<i data-lucide="video" class="w-5 h-5"></i>
|
||||||
<tr>
|
{% trans "Meeting Information" %}
|
||||||
<td><strong>{% trans "Meeting ID:" %}</strong></td>
|
</h3>
|
||||||
<td>{{ interview.zoom_meeting.meeting_id }}</td>
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
</tr>
|
<div class="flex">
|
||||||
<tr>
|
<span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Meeting ID:" %}</span>
|
||||||
<td><strong>{% trans "Topic:" %}</strong></td>
|
<span class="text-gray-900 font-mono">{{ interview.zoom_meeting.meeting_id }}</span>
|
||||||
<td>{{ interview.zoom_meeting.topic }}</td>
|
</div>
|
||||||
</tr>
|
<div class="flex">
|
||||||
<tr>
|
<span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Topic:" %}</span>
|
||||||
<td><strong>{% trans "Duration:" %}</strong></td>
|
<span class="text-gray-900">{{ interview.zoom_meeting.topic }}</span>
|
||||||
<td>{{ interview.zoom_meeting.duration }} {% trans "minutes" %}</td>
|
</div>
|
||||||
</tr>
|
<div class="flex">
|
||||||
<tr>
|
<span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Duration:" %}</span>
|
||||||
<td><strong>{% trans "Join URL:" %}</strong></td>
|
<span class="text-gray-900">{{ interview.zoom_meeting.duration }} {% trans "minutes" %}</span>
|
||||||
<td><a href="{{ interview.zoom_meeting.join_url }}" target="_blank">{{ interview.zoom_meeting.join_url }}</a></td>
|
</div>
|
||||||
</tr>
|
<div class="flex">
|
||||||
</table>
|
<span class="font-semibold text-gray-700 w-32 flex-shrink-0">{% trans "Join URL:" %}</span>
|
||||||
|
<a href="{{ interview.zoom_meeting.join_url }}" target="_blank"
|
||||||
|
class="text-temple-red hover:text-red-700 transition font-medium">
|
||||||
|
{{ interview.zoom_meeting.join_url|truncatechars:50 }}
|
||||||
|
<i data-lucide="external-link" class="w-3 h-3 inline ml-1"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="mt-4">
|
<!-- Action Buttons -->
|
||||||
<div class="d-flex gap-2">
|
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||||
<a href="{% url 'interview_calendar' slug=job.slug %}" class="btn btn-secondary">
|
<div class="flex flex-wrap gap-3">
|
||||||
<i class="fas fa-calendar"></i> {% trans "Back to Calendar" %}
|
<a href="{% url 'interview_calendar' slug=job.slug %}"
|
||||||
|
class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="calendar" class="w-4 h-4"></i>
|
||||||
|
{% trans "Back to Calendar" %}
|
||||||
</a>
|
</a>
|
||||||
{% if interview.status == 'scheduled' %}
|
{% if interview.status == 'scheduled' %}
|
||||||
<button class="btn btn-success">
|
<button type="button"
|
||||||
<i class="fas fa-check"></i> {% trans "Confirm Interview" %}
|
class="inline-flex items-center gap-2 bg-green-500 hover:bg-green-600 text-white px-4 py-2.5 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="check" class="w-4 h-4"></i>
|
||||||
|
{% trans "Confirm Interview" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if interview.status != 'cancelled' and interview.status != 'completed' %}
|
{% if interview.status != 'cancelled' and interview.status != 'completed' %}
|
||||||
<button class="btn btn-danger">
|
<button type="button"
|
||||||
<i class="fas fa-times"></i> {% trans "Cancel Interview" %}
|
class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2.5 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Cancel Interview" %}
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -147,3 +133,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block customJS %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize Lucide icons
|
||||||
|
lucide.createIcons();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -1,66 +1,75 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "Mark All as Read" %} - ATS{% endblock %}
|
{% block title %}{% trans "Mark All as Read" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<div class="row justify-content-center">
|
<div class="flex justify-center">
|
||||||
<div class="col-md-6">
|
<div class="w-full max-w-2xl">
|
||||||
<div class="card">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8 text-center">
|
||||||
<div class="card-body text-center p-5">
|
<div class="mb-6">
|
||||||
<div class="mb-4">
|
<div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto">
|
||||||
<i class="fas fa-check-double fa-3x text-success"></i>
|
<i data-lucide="check-circle-2" class="w-12 h-12 text-green-600"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="mb-3">{{ title }}</h4>
|
<h4 class="text-2xl font-bold text-gray-900 mb-3">{{ title }}</h4>
|
||||||
<p class="text-muted mb-4">{{ message }}</p>
|
<p class="text-gray-600 mb-6">{{ message }}</p>
|
||||||
|
|
||||||
{% if unread_count > 0 %}
|
{% if unread_count > 0 %}
|
||||||
<div class="alert alert-info mb-4">
|
<div class="bg-blue-50 border border-blue-200 rounded-xl p-5 mb-6 text-left">
|
||||||
<h6 class="alert-heading">
|
<h6 class="text-lg font-bold text-blue-800 flex items-center gap-2 mb-3">
|
||||||
<i class="fas fa-info-circle me-2"></i>{% trans "What this will do" %}
|
<i data-lucide="info" class="w-5 h-5"></i>
|
||||||
|
{% trans "What this will do" %}
|
||||||
</h6>
|
</h6>
|
||||||
<p class="mb-2">
|
<p class="text-blue-700 mb-3">
|
||||||
{% blocktrans count count=unread_count %}
|
{% blocktrans count count=unread_count %}
|
||||||
This will mark {{ count }} unread notification as read.
|
This will mark {{ count }} unread notification as read.
|
||||||
{% plural %}
|
{% plural %}
|
||||||
This will mark all {{ count }} unread notifications as read.
|
This will mark all {{ count }} unread notifications as read.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
<p class="mb-0">
|
<p class="text-blue-700">
|
||||||
{% trans "You can still view all notifications in your notification list, but they won't appear as unread." %}
|
{% trans "You can still view all notifications in your notification list, but they won't appear as unread." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="alert alert-success mb-4">
|
<div class="bg-green-50 border border-green-200 rounded-xl p-5 mb-6 text-left">
|
||||||
<h6 class="alert-heading">
|
<h6 class="text-lg font-bold text-green-800 flex items-center gap-2 mb-2">
|
||||||
<i class="fas fa-check-circle me-2"></i>{% trans "All caught up!" %}
|
<i data-lucide="check-circle" class="w-5 h-5"></i>
|
||||||
|
{% trans "All caught up!" %}
|
||||||
</h6>
|
</h6>
|
||||||
<p class="mb-0">
|
<p class="text-green-700">
|
||||||
{% trans "You don't have any unread notifications to mark as read." %}
|
{% trans "You don't have any unread notifications to mark as read." %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if unread_count > 0 %}
|
{% if unread_count > 0 %}
|
||||||
<form method="post" class="d-inline">
|
<form method="post" class="inline-flex gap-3">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-success">
|
<button type="submit" class="inline-flex items-center gap-2 bg-green-500 hover:bg-green-600 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-check-double me-1"></i> {% trans "Yes, Mark All as Read" %}
|
<i data-lucide="check-circle-2" class="w-4 h-4"></i>
|
||||||
|
{% trans "Yes, Mark All as Read" %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{{ cancel_url }}" class="btn btn-outline-secondary">
|
<a href="{{ cancel_url }}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-6 py-2.5 rounded-xl transition">
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ cancel_url }}" class="btn btn-primary">
|
<a href="{{ cancel_url }}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Notifications" %}
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
{% trans "Back to Notifications" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,41 +1,47 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "Delete Notification" %} - ATS{% endblock %}
|
{% block title %}{% trans "Delete Notification" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<div class="row justify-content-center">
|
<div class="flex justify-center">
|
||||||
<div class="col-md-6">
|
<div class="w-full max-w-2xl">
|
||||||
<div class="card">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 p-8 text-center">
|
||||||
<div class="card-body text-center p-5">
|
<div class="mb-6">
|
||||||
<div class="mb-4">
|
<div class="w-20 h-20 bg-amber-100 rounded-full flex items-center justify-center mx-auto">
|
||||||
<i class="fas fa-exclamation-triangle fa-3x text-warning"></i>
|
<i data-lucide="alert-triangle" class="w-12 h-12 text-amber-500"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h4 class="mb-3">{{ title }}</h4>
|
<h4 class="text-2xl font-bold text-gray-900 mb-3">{{ title }}</h4>
|
||||||
<p class="text-muted mb-4">{{ message }}</p>
|
<p class="text-gray-600 mb-6">{{ message }}</p>
|
||||||
|
|
||||||
<div class="alert alert-light mb-4">
|
<div class="bg-gray-50 border border-gray-200 rounded-xl p-5 mb-6 text-left">
|
||||||
<h6 class="alert-heading">{% trans "Notification Preview" %}</h6>
|
<h6 class="text-lg font-bold text-gray-800 mb-3">{% trans "Notification Preview" %}</h6>
|
||||||
<p class="mb-2"><strong>{% trans "Message:" %}</strong> {{ notification.message|truncatewords:20 }}</p>
|
<p class="mb-2"><strong class="text-gray-700">{% trans "Message:" %}</strong> {{ notification.message|truncatewords:20 }}</p>
|
||||||
<p class="mb-0">
|
<p>
|
||||||
<strong>{% trans "Created:" %}</strong> {{ notification.created_at|date:"Y-m-d H:i" }}
|
<strong class="text-gray-700">{% trans "Created:" %}</strong> {{ notification.created_at|date:"Y-m-d H:i" }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" class="d-inline">
|
<form method="post" class="inline-flex gap-3">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<button type="submit" class="btn btn-danger">
|
<button type="submit" class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Yes, Delete" %}
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
|
{% trans "Yes, Delete" %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{{ cancel_url }}" class="btn btn-outline-secondary">
|
<a href="{{ cancel_url }}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-6 py-2.5 rounded-xl transition">
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,63 +1,78 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "Notifications" %} - ATS{% endblock %}
|
{% block title %}{% trans "Notifications" %} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="px-4 py-6">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<!-- Header -->
|
||||||
<div>
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-6">
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<div class="flex-1">
|
||||||
<i class="fas fa-bell me-2"></i>
|
<h1 class="text-3xl font-bold text-temple-dark mb-1 flex items-center gap-2">
|
||||||
|
<i data-lucide="bell" class="w-8 h-8"></i>
|
||||||
{% trans "Notifications" %}
|
{% trans "Notifications" %}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">
|
<p class="text-gray-600">
|
||||||
{% blocktrans count count=total_notifications %}
|
{% blocktrans count count=total_notifications %}
|
||||||
{{ count }} notification
|
{{ count }} notification
|
||||||
{% plural %}
|
{% plural %}
|
||||||
{{ count }} notifications
|
{{ count }} notifications
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% if unread_notifications %}({{ unread_notifications }} unread){% endif %}
|
{% if unread_notifications %}
|
||||||
|
<span class="font-semibold text-temple-red">({{ unread_notifications }} unread)</span>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex gap-2">
|
<div class="flex gap-2">
|
||||||
{% if unread_notifications %}
|
{% if unread_notifications %}
|
||||||
<a href="{% url 'notification_mark_all_read' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'notification_mark_all_read' %}"
|
||||||
<i class="fas fa-check-double me-1"></i> {% trans "Mark All Read" %}
|
class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="check-double" class="w-4 h-4"></i>
|
||||||
|
<span class="hidden sm:inline">{% trans "Mark All Read" %}</span>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="card mb-4">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200 mb-6">
|
||||||
<div class="card-body">
|
<div class="p-6">
|
||||||
<form method="get" class="row g-3">
|
<form method="get" class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div class="col-md-3">
|
<div>
|
||||||
<label for="status_filter" class="form-label">{% trans "Status" %}</label>
|
<label for="status_filter" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
<select name="status" id="status_filter" class="form-select">
|
{% trans "Status" %}
|
||||||
|
</label>
|
||||||
|
<select name="status" id="status_filter"
|
||||||
|
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900">
|
||||||
<option value="">{% trans "All Status" %}</option>
|
<option value="">{% trans "All Status" %}</option>
|
||||||
<option value="unread" {% if status_filter == 'unread' %}selected{% endif %}>{% trans "Unread" %}</option>
|
<option value="unread" {% if status_filter == 'unread' %}selected{% endif %}>{% trans "Unread" %}</option>
|
||||||
<option value="read" {% if status_filter == 'read' %}selected{% endif %}>{% trans "Read" %}</option>
|
<option value="read" {% if status_filter == 'read' %}selected{% endif %}>{% trans "Read" %}</option>
|
||||||
<option value="sent" {% if status_filter == 'sent' %}selected{% endif %}>{% trans "Sent" %}</option>
|
<option value="sent" {% if status_filter == 'sent' %}selected{% endif %}>{% trans "Sent" %}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3">
|
<div>
|
||||||
<label for="type_filter" class="form-label">{% trans "Type" %}</label>
|
<label for="type_filter" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
<select name="type" id="type_filter" class="form-select">
|
{% trans "Type" %}
|
||||||
|
</label>
|
||||||
|
<select name="type" id="type_filter"
|
||||||
|
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900">
|
||||||
<option value="">{% trans "All Types" %}</option>
|
<option value="">{% trans "All Types" %}</option>
|
||||||
<option value="in_app" {% if type_filter == 'in_app' %}selected{% endif %}>{% trans "In-App" %}</option>
|
<option value="in_app" {% if type_filter == 'in_app' %}selected{% endif %}>{% trans "In-App" %}</option>
|
||||||
<option value="email" {% if type_filter == 'email' %}selected{% endif %}>{% trans "Email" %}</option>
|
<option value="email" {% if type_filter == 'email' %}selected{% endif %}>{% trans "Email" %}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<label class="form-label"> </label>
|
<label class="block text-sm font-semibold text-gray-700 mb-2"> </label>
|
||||||
<div class="d-flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button type="submit" class="btn btn-main-action">
|
<button type="submit"
|
||||||
<i class="fas fa-filter me-1"></i> {% trans "Filter" %}
|
class="flex-1 bg-temple-red hover:bg-red-800 text-white font-semibold px-4 py-2.5 rounded-lg transition shadow-md hover:shadow-lg flex items-center justify-center gap-2">
|
||||||
|
<i data-lucide="filter" class="w-4 h-4"></i>
|
||||||
|
{% trans "Filter" %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'notification_list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'notification_list' %}"
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
class="flex-1 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg transition flex items-center justify-center gap-2">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Clear" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -66,81 +81,72 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Statistics -->
|
<!-- Statistics -->
|
||||||
<div class="row mb-4">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
<div class="col-md-4">
|
<div class="bg-white rounded-xl shadow-md border border-temple-red p-6 text-center">
|
||||||
<div class="card border-primary">
|
<h3 class="text-3xl font-bold text-temple-red mb-1">{{ total_notifications }}</h3>
|
||||||
<div class="card-body text-center">
|
<p class="text-gray-600">{% trans "Total Notifications" %}</p>
|
||||||
<h5 class="card-title text-primary">{{ total_notifications }}</h5>
|
|
||||||
<p class="card-text">{% trans "Total Notifications" %}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card border-warning">
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<h5 class="card-title text-warning">{{ unread_notifications }}</h5>
|
|
||||||
<p class="card-text">{% trans "Unread" %}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<div class="card border-info">
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<h5 class="card-title text-info">{{ email_notifications }}</h5>
|
|
||||||
<p class="card-text">{% trans "Email Notifications" %}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-white rounded-xl shadow-md border border-yellow-500 p-6 text-center">
|
||||||
|
<h3 class="text-3xl font-bold text-yellow-600 mb-1">{{ unread_notifications }}</h3>
|
||||||
|
<p class="text-gray-600">{% trans "Unread" %}</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-white rounded-xl shadow-md border border-blue-500 p-6 text-center">
|
||||||
|
<h3 class="text-3xl font-bold text-blue-600 mb-1">{{ email_notifications }}</h3>
|
||||||
|
<p class="text-gray-600">{% trans "Email Notifications" %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Notifications List -->
|
<!-- Notifications List -->
|
||||||
{% if page_obj %}
|
{% if page_obj %}
|
||||||
<div class="card">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||||
<div class="card-body p-0">
|
<div class="divide-y divide-gray-200">
|
||||||
<div class="list-group list-group-flush">
|
|
||||||
{% for notification in page_obj %}
|
{% for notification in page_obj %}
|
||||||
<div class="list-group-item list-group-item-action {% if notification.status == 'PENDING' %}bg-light{% endif %}">
|
<div class="p-4 hover:bg-gray-50 transition {% if notification.status == 'PENDING' %}bg-blue-50{% endif %}">
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="flex justify-between items-start gap-4">
|
||||||
<div class="flex-grow-1">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<div class="flex items-center flex-wrap gap-2 mb-2">
|
||||||
<span class="badge bg-{{ notification.get_status_bootstrap_class }} me-2">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||||
|
{% if notification.status == 'PENDING' %}bg-yellow-100 text-yellow-800
|
||||||
|
{% elif notification.status == 'READ' %}bg-green-100 text-green-800
|
||||||
|
{% else %}bg-gray-100 text-gray-800{% endif %}">
|
||||||
{{ notification.get_status_display }}
|
{{ notification.get_status_display }}
|
||||||
</span>
|
</span>
|
||||||
<span class="badge bg-secondary me-2">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-200 text-gray-800">
|
||||||
{{ notification.get_notification_type_display }}
|
{{ notification.get_notification_type_display }}
|
||||||
</span>
|
</span>
|
||||||
<small class="text-muted">{{ notification.created_at|date:"Y-m-d H:i" }}</small>
|
<span class="text-xs text-gray-500">{{ notification.created_at|date:"Y-m-d H:i" }}</span>
|
||||||
</div>
|
</div>
|
||||||
<h6 class="mb-1">
|
<h5 class="text-base font-semibold mb-1">
|
||||||
<a href="{% url 'notification_detail' notification.id %}" class="text-decoration-none {% if notification.status == 'PENDING' %}fw-bold{% endif %}">
|
<a href="{% url 'notification_detail' notification.id %}"
|
||||||
|
class="text-gray-900 hover:text-temple-red transition {% if notification.status == 'PENDING' %}font-bold{% endif %}">
|
||||||
{{ notification.message|truncatewords:15 }}
|
{{ notification.message|truncatewords:15 }}
|
||||||
</a>
|
</a>
|
||||||
</h6>
|
</h5>
|
||||||
{% if notification.related_meeting %}
|
{% if notification.related_meeting %}
|
||||||
<small class="text-muted">
|
<p class="text-sm text-gray-500">
|
||||||
<i class="fas fa-video me-1"></i>
|
<i data-lucide="video" class="w-3 h-3 inline mr-1"></i>
|
||||||
{% trans "Related to meeting:" %} {{ notification.related_meeting.topic }}
|
{% trans "Related to meeting:" %} {{ notification.related_meeting.topic }}
|
||||||
</small>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column gap-1">
|
<div class="flex flex-col gap-2">
|
||||||
{% if notification.status == 'PENDING' %}
|
{% if notification.status == 'PENDING' %}
|
||||||
<a href="{% url 'notification_mark_read' notification.id %}"
|
<a href="{% url 'notification_mark_read' notification.id %}"
|
||||||
class="btn btn-sm btn-outline-success"
|
class="p-2 text-green-600 hover:bg-green-50 rounded-lg transition"
|
||||||
title="{% trans 'Mark as read' %}">
|
title="{% trans 'Mark as read' %}">
|
||||||
<i class="fas fa-check"></i>
|
<i data-lucide="check" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'notification_mark_unread' notification.id %}"
|
<a href="{% url 'notification_mark_unread' notification.id %}"
|
||||||
class="btn btn-sm btn-outline-secondary"
|
class="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition"
|
||||||
title="{% trans 'Mark as unread' %}">
|
title="{% trans 'Mark as unread' %}">
|
||||||
<i class="fas fa-envelope"></i>
|
<i data-lucide="mail" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'notification_delete' notification.id %}"
|
<a href="{% url 'notification_delete' notification.id %}"
|
||||||
class="btn btn-sm btn-outline-danger"
|
class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition"
|
||||||
title="{% trans 'Delete notification' %}">
|
title="{% trans 'Delete notification' %}">
|
||||||
<i class="fas fa-trash"></i>
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -148,36 +154,38 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
{% if page_obj.has_other_pages %}
|
{% if page_obj.has_other_pages %}
|
||||||
<nav aria-label="{% trans 'Notifications pagination' %}" class="mt-4">
|
<nav aria-label="{% trans 'Notifications pagination' %}" class="mt-6">
|
||||||
<ul class="pagination justify-content-center">
|
<ul class="flex justify-center items-center gap-2">
|
||||||
{% if page_obj.has_previous %}
|
{% if page_obj.has_previous %}
|
||||||
<li class="page-item">
|
<li>
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}&status={{ status_filter }}&type={{ type_filter }}">
|
<a class="px-3 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
|
||||||
<i class="fas fa-chevron-left"></i>
|
href="?page={{ page_obj.previous_page_number }}&status={{ status_filter }}&type={{ type_filter }}">
|
||||||
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for num in page_obj.paginator.page_range %}
|
{% for num in page_obj.paginator.page_range %}
|
||||||
{% if page_obj.number == num %}
|
{% if page_obj.number == num %}
|
||||||
<li class="page-item active">
|
<li>
|
||||||
<span class="page-link">{{ num }}</span>
|
<span class="px-4 py-2 bg-temple-red text-white rounded-lg font-semibold">{{ num }}</span>
|
||||||
</li>
|
</li>
|
||||||
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
||||||
<li class="page-item">
|
<li>
|
||||||
<a class="page-link" href="?page={{ num }}&status={{ status_filter }}&type={{ type_filter }}">{{ num }}</a>
|
<a class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
|
||||||
|
href="?page={{ num }}&status={{ status_filter }}&type={{ type_filter }}">{{ num }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
{% if page_obj.has_next %}
|
||||||
<li class="page-item">
|
<li>
|
||||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}&status={{ status_filter }}&type={{ type_filter }}">
|
<a class="px-3 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition"
|
||||||
<i class="fas fa-chevron-right"></i>
|
href="?page={{ page_obj.next_page_number }}&status={{ status_filter }}&type={{ type_filter }}">
|
||||||
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -185,10 +193,10 @@
|
|||||||
</nav>
|
</nav>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-5">
|
<div class="text-center py-12 bg-white rounded-xl shadow-md border border-gray-200">
|
||||||
<i class="fas fa-bell-slash fa-3x text-muted mb-3"></i>
|
<i data-lucide="bell-off" class="w-16 h-16 text-gray-400 mx-auto mb-4"></i>
|
||||||
<h5 class="text-muted">{% trans "No notifications found" %}</h5>
|
<h3 class="text-xl font-semibold text-gray-600 mb-2">{% trans "No notifications found" %}</h3>
|
||||||
<p class="text-muted">
|
<p class="text-gray-500 mb-4">
|
||||||
{% if status_filter or type_filter %}
|
{% if status_filter or type_filter %}
|
||||||
{% trans "Try adjusting your filters to see more notifications." %}
|
{% trans "Try adjusting your filters to see more notifications." %}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -196,8 +204,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% if status_filter or type_filter %}
|
{% if status_filter or type_filter %}
|
||||||
<a href="{% url 'notification_list' %}" class="btn btn-main-action">
|
<a href="{% url 'notification_list' %}"
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Clear Filters" %}
|
class="inline-flex items-center gap-2 bg-temple-red hover:bg-red-800 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-md hover:shadow-lg">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Clear Filters" %}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -207,8 +217,11 @@
|
|||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
/*
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize Lucide icons
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
|
/*
|
||||||
// Auto-refresh notifications every 30 seconds
|
// Auto-refresh notifications every 30 seconds
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
fetch('/api/notification-count/')
|
fetch('/api/notification-count/')
|
||||||
@ -219,15 +232,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (badge) {
|
if (badge) {
|
||||||
badge.textContent = data.count;
|
badge.textContent = data.count;
|
||||||
if (data.count > 0) {
|
if (data.count > 0) {
|
||||||
badge.classList.remove('d-none');
|
badge.classList.remove('hidden');
|
||||||
} else {
|
} else {
|
||||||
badge.classList.add('d-none');
|
badge.classList.add('hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => console.error('Error fetching notifications:', error));
|
.catch(error => console.error('Error fetching notifications:', error));
|
||||||
}, 30000);
|
}, 30000);
|
||||||
});
|
|
||||||
*/
|
*/
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,40 +1,89 @@
|
|||||||
{% load i18n crispy_forms_tags %}
|
{% load i18n crispy_forms_tags %}
|
||||||
<div class="p-3">
|
|
||||||
|
<div class="p-4">
|
||||||
<form hx-boost="true" id="noteform" action="{{ url }}" method="post" hx-select=".note-table-body" hx-target=".note-table-body" hx-swap="outerHTML" hx-push-url="false">
|
<form hx-boost="true" id="noteform" action="{{ url }}" method="post" hx-select=".note-table-body" hx-target=".note-table-body" hx-swap="outerHTML" hx-push-url="false">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<!-- Crispy Form for rendering -->
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
{{ form|crispy }}
|
{{ form|crispy }}
|
||||||
<div class="modal-footer">
|
</div>
|
||||||
<button type="button" id="notesubmit" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
|
||||||
<button type="submit" class="btn btn-main-action" id="saveNoteBtn">{% trans "Save Note" %}</button>
|
<!-- Form Actions -->
|
||||||
|
<div class="flex justify-end gap-2 pt-4 border-t-2 border-gray-200">
|
||||||
|
|
||||||
|
<button type="submit" class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium" id="saveNoteBtn">
|
||||||
|
<i data-lucide="save" class="w-4 h-4 inline mr-1"></i>
|
||||||
|
{% trans "Save Note" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div class="table-responsive mt-3">
|
</div>
|
||||||
<table class="table table-sm" id="notesTable">
|
|
||||||
|
<!-- Notes Table Section -->
|
||||||
|
<div class="mt-6">
|
||||||
|
<!-- Table Header -->
|
||||||
|
<div class="bg-gray-50 border border-gray-200 rounded-xl overflow-hidden">
|
||||||
|
<table class="w-full border-collapse" id="notesTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr class="border-b-2 border-temple-red">
|
||||||
<th scope="col">{% trans "Author" %}</th>
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark">
|
||||||
<th scope="col" style="width: 60%;">{% trans "Note" %}</th>
|
<i data-lucide="user" class="w-3 h-3 inline mr-1"></i>
|
||||||
<th scope="col">{% trans "Created" %}</th>
|
{% trans "Author" %}
|
||||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark" style="width: 60%;">
|
||||||
|
<i data-lucide="sticky-note" class="w-3 h-3 inline mr-1"></i>
|
||||||
|
{% trans "Note" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark">
|
||||||
|
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
||||||
|
{% trans "Created" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-semibold text-temple-dark">
|
||||||
|
<i data-lucide="trash-2" class="w-3 h-3 inline mr-1"></i>
|
||||||
|
{% trans "Actions" %}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="note-table-body">
|
<tbody class="note-table-body">
|
||||||
{% if notes %}
|
{% if notes %}
|
||||||
{% for note in notes %}
|
{% for note in notes %}
|
||||||
<tr id="note-{{ note.id }}">
|
<tr id="note-{{ note.id }}" class="hover:bg-gray-50 transition-colors border-b border-gray-200">
|
||||||
<td class="align-middle">
|
<td class="px-4 py-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-temple-red text-white flex items-center justify-center text-sm font-bold">
|
||||||
|
{{ note.author.first_name.0|default:note.author.username.0|upper }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-medium text-temple-dark text-sm">
|
||||||
{{ note.author.get_full_name|default:note.author.username }}
|
{{ note.author.get_full_name|default:note.author.username }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle">
|
<td class="px-4 py-3">
|
||||||
|
<div class="text-sm text-gray-700 bg-white p-3 rounded-lg border border-gray-200 shadow-sm">
|
||||||
{{ note.content|linebreaksbr }}
|
{{ note.content|linebreaksbr }}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle text-nowrap">
|
<td class="px-4 py-3 whitespace-nowrap">
|
||||||
<span class="text-muted">
|
<div class="text-xs text-gray-500">
|
||||||
|
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
||||||
{{ note.created_at|date:"SHORT_DATETIME_FORMAT" }}
|
{{ note.created_at|date:"SHORT_DATETIME_FORMAT" }}
|
||||||
</span>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle text-end">
|
<td class="px-4 py-3 text-right">
|
||||||
<button hx-delete="{% url 'delete_note' note.slug %}" hx-target="#note-{{ note.id }}" hx-swap="delete" type="button" class="btn btn-sm btn-outline-danger delete-note-btn">
|
<button hx-delete="{% url 'delete_note' note.slug %}"
|
||||||
|
hx-target="#note-{{ note.id }}"
|
||||||
|
hx-swap="delete"
|
||||||
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
|
hx-confirm="{% trans 'Are you sure you want to delete this note?' %}"
|
||||||
|
type="button"
|
||||||
|
class="px-3 py-1.5 border-2 border-red-500 text-red-500 rounded-lg hover:bg-red-500 hover:text-white transition text-xs font-medium delete-note-btn inline-flex items-center gap-1"
|
||||||
|
title="{% trans 'Delete Note' %}">
|
||||||
|
<i data-lucide="trash-2" class="w-3 h-3"></i>
|
||||||
{% trans "Delete" %}
|
{% trans "Delete" %}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@ -42,10 +91,115 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="text-center text-muted py-3">{% trans "No notes yet." %}</td>
|
<td colspan="4" class="text-center py-8 text-gray-500">
|
||||||
|
<div class="flex flex-col items-center gap-2">
|
||||||
|
<i data-lucide="sticky-note" class="w-12 h-12 text-gray-400"></i>
|
||||||
|
<span class="text-sm">{% trans "No notes yet." %}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Add custom styling for crispy form -->
|
||||||
|
<style>
|
||||||
|
#noteform textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding:1rem 1.25rem;
|
||||||
|
background-color: white;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #374151;
|
||||||
|
transition: all 0.3s;
|
||||||
|
resize: none;
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteform textarea:focus {
|
||||||
|
border-color: #dc2626;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.1);
|
||||||
|
background-color: #fef2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteform textarea::placeholder {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteform textarea:hover {
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||||
|
border-color: #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteform label {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteform .help-block,
|
||||||
|
#noteform .helptext {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#noteform .text-danger,
|
||||||
|
#noteform .errorlist {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #dc2626;
|
||||||
|
background-color: #fef2f2;
|
||||||
|
border: 2px solid #fca5a5;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-top: 0.75rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Reinitialize Lucide icons after HTMX updates
|
||||||
|
document.addEventListener('htmx:afterSwap', function(evt) {
|
||||||
|
if (typeof lucide !== 'undefined') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Function to close the modal
|
||||||
|
function closeModal() {
|
||||||
|
const modal = document.getElementById('noteModal');
|
||||||
|
if (modal) {
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(modal);
|
||||||
|
if (modalInstance) {
|
||||||
|
modalInstance.hide();
|
||||||
|
} else {
|
||||||
|
new bootstrap.Modal(modal).hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel button click handler - attached to document to catch dynamically added buttons
|
||||||
|
document.body.addEventListener('click', function(e) {
|
||||||
|
const cancelBtn = e.target.closest('#cancelNoteBtn')
|
||||||
|
if (cancelBtn) {
|
||||||
|
e.preventDefault();
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-attach cancel button listener after HTMX updates (for the form submit cancel)
|
||||||
|
document.addEventListener('htmx:afterSwap', function(evt) {
|
||||||
|
const cancelBtn = document.getElementById('cancelNoteBtn');
|
||||||
|
if (cancelBtn) {
|
||||||
|
cancelBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
closeModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@ -1,12 +1,17 @@
|
|||||||
|
{% load static i18n %}
|
||||||
<div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true">
|
<div class="modal fade" id="noteModal" tabindex="-1" aria-labelledby="noteModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-lg" role="document">
|
<div class="modal-dialog modal-lg" role="document">
|
||||||
<div class="modal-content kaauh-card">
|
<div class="modal-content bg-white border border-gray-200 rounded-xl shadow-lg">
|
||||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
<div class="modal-header flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<h5 class="modal-title text-lg font-bold text-temple-dark" id="noteModalLabel">
|
||||||
</div>
|
<i data-lucide="sticky-note" class="w-5 h-5 inline mr-2"></i>{% trans "Add Note" %}
|
||||||
<div class="modal-body notemodal">
|
</h5>
|
||||||
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" data-bs-dismiss="modal" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal-body notemodal p-6">
|
||||||
|
<!-- Content will be loaded via HTMX -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,32 +1,34 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<div class="modal fade" id="stageConfirmationModal" tabindex="-1" aria-labelledby="stageConfirmationModalLabel" aria-hidden="true">
|
<div class="modal fade" id="stageConfirmationModal" tabindex="-1" aria-labelledby="stageConfirmationModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
<div class="modal-content kaauh-card">
|
<div class="modal-content bg-white border border-gray-200 rounded-xl shadow-lg">
|
||||||
<div class="modal-header" style="border-bottom: 1px solid var(--kaauh-border);">
|
<div class="modal-header flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
<h5 class="modal-title" id="stageConfirmationModalLabel" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h5 class="modal-title text-lg font-bold text-temple-dark" id="stageConfirmationModalLabel">
|
||||||
<i class="fas fa-info-circle me-2"></i>{% trans "Confirm Stage Change" %}
|
<i data-lucide="info" class="w-5 h-5 inline mr-2"></i>{% trans "Confirm Stage Change" %}
|
||||||
</h5>
|
</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" data-bs-dismiss="modal" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body p-6">
|
||||||
<div class="d-flex align-items-center justify-content-center py-3 mb-3">
|
<div class="flex items-center justify-center py-3 mb-3">
|
||||||
<i class="fas fa-exchange-alt fa-4x" style="color: var(--kaauh-teal);"></i>
|
<i data-lucide="arrow-right-left" class="w-16 h-16 text-temple-red"></i>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-center mb-2" style="font-size: 1.1rem; color: var(--kaauh-primary-text);">
|
<p class="text-center mb-2 text-base text-gray-800">
|
||||||
<span id="stageConfirmationMessage">{% trans "Are you sure you want to change the stage?" %}</span>
|
<span id="stageConfirmationMessage">{% trans "Are you sure you want to change the stage?" %}</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="alert alert-info text-center" role="alert">
|
<div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg text-center" role="alert">
|
||||||
<i class="fas fa-user-check me-2"></i>
|
<i data-lucide="user-check" class="w-4 h-4 inline mr-2"></i>
|
||||||
<strong>{% trans "Selected Stage:" %}</strong>
|
<span class="font-semibold">{% trans "Selected Stage:" %}</span>
|
||||||
<span id="targetStageName" class="fw-bold">{% trans "--" %}</span>
|
<span id="targetStageName" class="font-bold">{% trans "--" %}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer" style="border-top: 1px solid var(--kaauh-border);">
|
<div class="modal-footer px-6 py-4 border-t border-gray-200 flex justify-end gap-2">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
<button type="button" class="px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" data-bs-dismiss="modal">
|
||||||
<i class="fas fa-times me-1"></i>{% trans "Cancel" %}
|
<i data-lucide="x" class="w-4 h-4 inline mr-1"></i>{% trans "Cancel" %}
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-main-action" id="confirmStageChangeButton">
|
<button type="button" class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium" id="confirmStageChangeButton">
|
||||||
<i class="fas fa-check me-1"></i>{% trans "Confirm" %}
|
<i data-lucide="check" class="w-4 h-4 inline mr-1"></i>{% trans "Confirm" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,92 +1,106 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "Schedule Meeting" %} - {{ job.title }} - ATS{% endblock %}
|
{% block title %}{% trans "Schedule Meeting" %} - {{ job.title }} - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container py-4">
|
<div class="px-4 py-6 max-w-2xl mx-auto">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<!-- Header -->
|
||||||
<div>
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4 mb-6">
|
||||||
<h1 class="h3 mb-1">
|
<div class="flex-1">
|
||||||
<i class="fas fa-calendar-plus me-2"></i>
|
<h1 class="text-2xl font-bold text-temple-dark mb-1 flex items-center gap-2">
|
||||||
|
<i data-lucide="calendar-plus" class="w-7 h-7"></i>
|
||||||
{% if has_future_meeting %}
|
{% if has_future_meeting %}
|
||||||
{% trans "Update Interview" %} for {{ application.name }}
|
{% trans "Update Interview" %} for {{ application.name }}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Schedule Interview" %} for {{ application.name }}
|
{% trans "Schedule Interview" %} for {{ application.name }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">{% trans "Job" %}: {{ job.title }}</p>
|
<p class="text-gray-600">{% trans "Job" %}: {{ job.title }}</p>
|
||||||
{% if has_future_meeting %}
|
{% if has_future_meeting %}
|
||||||
<div class="alert alert-info mt-2 mb-0" role="alert">
|
<div class="mt-3 bg-blue-50 border border-blue-200 rounded-lg p-3 flex items-start gap-2">
|
||||||
<i class="fas fa-info-circle me-1"></i>
|
<i data-lucide="info" class="w-5 h-5 text-blue-500 flex-shrink-0 mt-0.5"></i>
|
||||||
|
<p class="text-sm text-blue-800">
|
||||||
{% trans "This application has upcoming interviews. You are updating an existing schedule." %}
|
{% trans "This application has upcoming interviews. You are updating an existing schedule." %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'applications_interview_view' job.slug %}" class="btn btn-outline-secondary">
|
<a href="{% url 'applications_interview_view' job.slug %}"
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Candidates" %}
|
class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2.5 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
{% trans "Back to Candidates" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<!-- Form Card -->
|
||||||
<div class="card-body">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||||
<form method="post">
|
<div class="p-6">
|
||||||
|
<form method="post" id="meetingForm">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Topic Field -->
|
||||||
<label for="{{ form.topic.id_for_label }}" class="form-label">
|
<div class="mb-6">
|
||||||
|
<label for="{{ form.topic.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
{% trans "Meeting Topic" %}
|
{% trans "Meeting Topic" %}
|
||||||
</label>
|
</label>
|
||||||
{{ form.topic }}
|
{{ form.topic }}
|
||||||
{% if form.topic.errors %}
|
{% if form.topic.errors %}
|
||||||
<div class="text-danger">
|
<div class="mt-1 text-sm text-red-600">
|
||||||
{% for error in form.topic.errors %}
|
{% for error in form.topic.errors %}
|
||||||
<small>{{ error }}</small>
|
<p>{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="form-text">
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
{% trans "Default topic will be 'Interview: [Job Title] with [Candidate Name]' if left empty." %}
|
{% trans "Default topic will be 'Interview: [Job Title] with [Candidate Name]' if left empty." %}
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Start Time Field -->
|
||||||
<label for="{{ form.start_time.id_for_label }}" class="form-label">
|
<div class="mb-6">
|
||||||
|
<label for="{{ form.start_time.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
{% trans "Start Time" %}
|
{% trans "Start Time" %}
|
||||||
</label>
|
</label>
|
||||||
{{ form.start_time }}
|
{{ form.start_time }}
|
||||||
{% if form.start_time.errors %}
|
{% if form.start_time.errors %}
|
||||||
<div class="text-danger">
|
<div class="mt-1 text-sm text-red-600">
|
||||||
{% for error in form.start_time.errors %}
|
{% for error in form.start_time.errors %}
|
||||||
<small>{{ error }}</small>
|
<p>{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="form-text">
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
{% trans "Please select a date and time for the interview." %}
|
{% trans "Please select a date and time for interview." %}
|
||||||
</div>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4">
|
<!-- Duration Field -->
|
||||||
<label for="{{ form.duration.id_for_label }}" class="form-label">
|
<div class="mb-8">
|
||||||
|
<label for="{{ form.duration.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
{% trans "Duration (minutes)" %}
|
{% trans "Duration (minutes)" %}
|
||||||
</label>
|
</label>
|
||||||
{{ form.duration }}
|
{{ form.duration }}
|
||||||
{% if form.duration.errors %}
|
{% if form.duration.errors %}
|
||||||
<div class="text-danger">
|
<div class="mt-1 text-sm text-red-600">
|
||||||
{% for error in form.duration.errors %}
|
{% for error in form.duration.errors %}
|
||||||
<small>{{ error }}</small>
|
<p>{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<!-- Action Buttons -->
|
||||||
<button type="submit" class="btn btn-primary">
|
<div class="flex flex-col sm:flex-row gap-3 pt-4 border-t border-gray-200">
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Schedule Meeting" %}
|
<button type="submit"
|
||||||
|
class="flex-1 sm:flex-none bg-temple-red hover:bg-red-800 text-white font-semibold px-8 py-3 rounded-xl transition shadow-md hover:shadow-lg flex items-center justify-center gap-2">
|
||||||
|
<i data-lucide="save" class="w-5 h-5"></i>
|
||||||
|
{% trans "Schedule Meeting" %}
|
||||||
</button>
|
</button>
|
||||||
<a href="{% url 'applications_interview_view' job.slug %}" class="btn btn-secondary">
|
<a href="{% url 'applications_interview_view' job.slug %}"
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
class="flex-1 sm:flex-none border border-gray-300 text-gray-700 hover:bg-gray-50 px-8 py-3 rounded-xl transition flex items-center justify-center gap-2">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -94,3 +108,101 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block customJS %}
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Initialize Lucide icons
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
|
// Add form styling to inputs
|
||||||
|
const formInputs = document.querySelectorAll('input, select, textarea');
|
||||||
|
formInputs.forEach(input => {
|
||||||
|
input.classList.add('w-full', 'px-3', 'py-2.5', 'border', 'border-gray-300', 'rounded-lg', 'focus:ring-2', 'focus:ring-temple-red', 'focus:border-transparent', 'transition', 'text-gray-900');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Form validation
|
||||||
|
const form = document.getElementById('meetingForm');
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
|
const topic = document.getElementById('id_topic');
|
||||||
|
const startTime = document.getElementById('id_start_time');
|
||||||
|
const duration = document.getElementById('id_duration');
|
||||||
|
|
||||||
|
// Add loading state
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 mr-2 animate-spin"></i> {% trans "Scheduling..." %}`;
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (startTime && !startTime.value) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Schedule Meeting" %}`;
|
||||||
|
alert('{% trans "Please select a start time." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duration && !duration.value) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Schedule Meeting" %}`;
|
||||||
|
alert('{% trans "Please specify the duration." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that start time is in the future
|
||||||
|
if (startTime && startTime.value) {
|
||||||
|
const selectedDate = new Date(startTime.value);
|
||||||
|
const now = new Date();
|
||||||
|
if (selectedDate <= now) {
|
||||||
|
e.preventDefault();
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Schedule Meeting" %}`;
|
||||||
|
alert('{% trans "Please select a future date and time for the interview." %}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn before leaving if form has data
|
||||||
|
let formChanged = false;
|
||||||
|
const inputsToTrack = form ? form.querySelectorAll('input, select, textarea') : [];
|
||||||
|
|
||||||
|
inputsToTrack.forEach(input => {
|
||||||
|
input.addEventListener('change', function() {
|
||||||
|
formChanged = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('beforeunload', function(e) {
|
||||||
|
if (formChanged) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.returnValue = '{% trans "You have unsaved changes. Are you sure you want to leave?" %}';
|
||||||
|
return e.returnValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
form.addEventListener('submit', function() {
|
||||||
|
formChanged = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set minimum date to today for datetime-local input
|
||||||
|
const startTimeInput = document.getElementById('id_start_time');
|
||||||
|
if (startTimeInput) {
|
||||||
|
const now = new Date();
|
||||||
|
const year = now.getFullYear();
|
||||||
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(now.getDate()).padStart(2, '0');
|
||||||
|
const hours = String(now.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||||
|
|
||||||
|
const minDateTime = `${year}-${month}-${day}T${hours}:${minutes}`;
|
||||||
|
startTimeInput.setAttribute('min', minDateTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@ -2,432 +2,555 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% block title %}{{ title }} - {{ block.super }}{% endblock %}
|
{% block title %}{{ title }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
/* UI Variables for the KAAT-S Theme */
|
/* Card Hover Effects */
|
||||||
:root {
|
.form-card {
|
||||||
--kaauh-teal: #00636e;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
--kaauh-teal-dark: #004a53;
|
|
||||||
--kaauh-border: #eaeff3;
|
|
||||||
--kaauh-gray-light: #f8f9fa;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Form Container Styling */
|
.form-card:hover {
|
||||||
.form-container {
|
transform: translateY(-2px);
|
||||||
max-width: 800px;
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Card Styling */
|
/* Button Hover Effects */
|
||||||
.card {
|
.btn-action {
|
||||||
border: 1px solid var(--kaauh-border);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main Action Button Style */
|
|
||||||
.btn-main-action {
|
|
||||||
background-color: var(--kaauh-teal);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.4rem;
|
|
||||||
padding: 0.5rem 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-main-action:hover {
|
.btn-action:hover {
|
||||||
background-color: var(--kaauh-teal-dark);
|
transform: translateY(-1px);
|
||||||
border-color: var(--kaauh-teal-dark);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Secondary Button Style */
|
.btn-primary {
|
||||||
.btn-outline-secondary {
|
transition: all 0.2s ease;
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
border-color: var(--kaauh-teal);
|
|
||||||
}
|
|
||||||
.btn-outline-secondary:hover {
|
|
||||||
background-color: var(--kaauh-teal-dark);
|
|
||||||
color: white;
|
|
||||||
border-color: var(--kaauh-teal-dark);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Form Field Styling */
|
.btn-primary:hover {
|
||||||
.form-control:focus {
|
transform: translateY(-1px);
|
||||||
border-color: var(--kaauh-teal);
|
box-shadow: 0 4px 12px rgba(157, 34, 53, 0.4);
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: rgba(157, 34, 53, 0.05);
|
||||||
|
border-color: rgba(157, 34, 53, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input Focus Animation */
|
||||||
|
.form-input {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Form Fields */
|
||||||
|
.form-field {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field:focus-within {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
color: #374151;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus {
|
||||||
|
border-color: #9d2235;
|
||||||
|
box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input::placeholder {
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:disabled {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
color: #9ca3af;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
||||||
|
background-position: right 0.75rem center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 1.5rem 1.5rem;
|
||||||
|
padding-right: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-select:focus {
|
.form-select:focus {
|
||||||
border-color: var(--kaauh-teal);
|
border-color: #9d2235;
|
||||||
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Breadcrumb Styling */
|
.form-select:disabled {
|
||||||
.breadcrumb {
|
background-color: #f9fafb;
|
||||||
background-color: transparent;
|
color: #9ca3af;
|
||||||
padding: 0;
|
cursor: not-allowed;
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item + .breadcrumb-item::before {
|
.form-textarea {
|
||||||
content: ">";
|
width: 100%;
|
||||||
color: var(--kaauh-teal);
|
padding: 0.75rem 1rem;
|
||||||
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
background-color: #ffffff;
|
||||||
|
color: #111827;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
outline: none;
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Alert Styling */
|
.form-textarea:focus {
|
||||||
.alert {
|
border-color: #9d2235;
|
||||||
border-radius: 0.5rem;
|
box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Loading State */
|
.form-helptext {
|
||||||
.btn.loading {
|
margin-top: 0.375rem;
|
||||||
position: relative;
|
font-size: 0.8rem;
|
||||||
pointer-events: none;
|
color: #6b7280;
|
||||||
opacity: 0.8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.loading::after {
|
.form-error {
|
||||||
content: "";
|
margin-top: 0.375rem;
|
||||||
position: absolute;
|
font-size: 0.8rem;
|
||||||
width: 16px;
|
color: #dc2626;
|
||||||
height: 16px;
|
font-weight: 500;
|
||||||
margin: auto;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
border-top-color: #ffffff;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% { transform: rotate(0deg); }
|
|
||||||
100% { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Current Profile Section */
|
|
||||||
.current-profile {
|
|
||||||
background-color: var(--kaauh-gray-light);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-profile h6 {
|
|
||||||
color: var(--kaauh-teal-dark);
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-image {
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: 50%;
|
|
||||||
border: 2px solid var(--kaauh-teal);
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="max-w-4xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||||
<div class="form-container">
|
|
||||||
<!-- Breadcrumb Navigation -->
|
<!-- Breadcrumb -->
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb" class="mb-6">
|
||||||
<ol class="breadcrumb">
|
<ol class="flex items-center gap-2 text-sm">
|
||||||
<li class="breadcrumb-item">
|
<li>
|
||||||
<a href="{% url 'source_list' %}" class="text-decoration-none text-secondary">
|
<a href="{% url 'source_list' %}"
|
||||||
<i class="fas fa-plug me-1"></i> {% trans "Sources" %}
|
class="text-gray-500 hover:text-temple-red transition-colors flex items-center gap-1">
|
||||||
|
<i data-lucide="database" class="w-4 h-4"></i>
|
||||||
|
{% trans "Sources" %}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% if source %}
|
{% if source %}
|
||||||
<li class="breadcrumb-item">
|
<li class="text-gray-400">/</li>
|
||||||
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none text-secondary">
|
<li>
|
||||||
|
<a href="{% url 'source_detail' source.pk %}"
|
||||||
|
class="text-gray-500 hover:text-temple-red transition-colors">
|
||||||
{{ source.name }}
|
{{ source.name }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="breadcrumb-item active" aria-current="page"
|
<li class="text-gray-400">/</li>
|
||||||
style="
|
<li class="text-temple-red font-semibold">{% trans "Update" %}</li>
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;">{% trans "Update" %}</li>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="breadcrumb-item active" aria-current="page"
|
<li class="text-gray-400">/</li>
|
||||||
style="
|
<li class="text-temple-red font-semibold">{% trans "Create" %}</li>
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;">{% trans "Create" %}</li>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ol>
|
</ol>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Page Header -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||||
<h4 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
|
||||||
<i class="fas fa-plug me-2"></i> {{ title }}
|
|
||||||
</h4>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
{% if source %}
|
|
||||||
<a href="{% url 'source_detail' source.pk %}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'source_delete' source.pk %}" class="btn btn-danger">
|
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if source %}
|
|
||||||
<!-- Current Source Info -->
|
|
||||||
<div class="card shadow-sm mb-4">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="current-profile">
|
|
||||||
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="current-image d-flex align-items-center justify-content-center bg-light">
|
|
||||||
<i class="fas fa-plug text-muted"></i>
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-1">{{ source.name }}</h5>
|
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
|
||||||
{% if source.source_type %}
|
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
||||||
<p class="text-muted mb-0">{% trans "Type" %}: {{ source.get_source_type_display }}</p>
|
{% if source %}
|
||||||
|
<i data-lucide="edit-3" class="w-6 h-6 text-temple-red"></i>
|
||||||
|
{% else %}
|
||||||
|
<i data-lucide="plus-circle" class="w-6 h-6 text-temple-red"></i>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if source.ip_address %}
|
</div>
|
||||||
<p class="text-muted mb-0">{% trans "IP Address" %}: {{ source.ip_address }}</p>
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-gray-500 text-sm sm:text-base">
|
||||||
|
{% if source %}
|
||||||
|
{% trans "Update integration source details" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Create a new integration source" %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<small class="text-muted">
|
</p>
|
||||||
{% trans "Created" %}: {{ source.created_at|date:"d M Y" }} •
|
|
||||||
{% trans "Last Updated" %}: {{ source.updated_at|date:"d M Y" }}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex gap-3">
|
||||||
|
{% if source %}
|
||||||
|
<a href="{% url 'source_detail' source.pk %}"
|
||||||
|
class="btn-secondary inline-flex items-center gap-2 px-5 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300">
|
||||||
|
<i data-lucide="eye" class="w-5 h-5"></i>
|
||||||
|
{% trans "View Details" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'source_delete' source.pk %}"
|
||||||
|
class="btn-danger inline-flex items-center gap-2 px-5 py-3 bg-red-500 text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
|
||||||
|
<i data-lucide="trash-2" class="w-5 h-5"></i>
|
||||||
|
{% trans "Delete" %}
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<a href="{% url 'source_list' %}"
|
||||||
|
class="btn-secondary inline-flex items-center gap-2 px-5 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300">
|
||||||
|
<i data-lucide="arrow-left" class="w-5 h-5"></i>
|
||||||
|
{% trans "Back to List" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Form Card -->
|
<!-- Form Card -->
|
||||||
<div class="card shadow-sm">
|
<div class="form-card bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<div class="card-body p-4">
|
<!-- Card Header -->
|
||||||
|
<div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
||||||
|
<i data-lucide="database" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-bold text-gray-900">{% trans "Source Configuration" %}</h2>
|
||||||
|
<p class="text-sm text-gray-500">{% trans "Fill in the integration details below" %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Card Body -->
|
||||||
|
<div class="p-6 sm:p-8">
|
||||||
|
|
||||||
|
<!-- Non-Field Errors -->
|
||||||
{% if form.non_field_errors %}
|
{% if form.non_field_errors %}
|
||||||
<div class="alert alert-danger" role="alert">
|
<div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl">
|
||||||
<h5 class="alert-heading">
|
<div class="flex items-start gap-3">
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
<div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center flex-shrink-0">
|
||||||
</h5>
|
<i data-lucide="alert-circle" class="w-5 h-5 text-red-500"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<h3 class="text-base font-bold text-red-700 mb-1">{% trans "Error" %}</h3>
|
||||||
{% for error in form.non_field_errors %}
|
{% for error in form.non_field_errors %}
|
||||||
<p class="mb-0">{{ error }}</p>
|
<p class="text-sm text-red-600">{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Messages -->
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
|
<div class="space-y-3 mb-6">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
<div class="flex items-start gap-3 p-4 rounded-xl border
|
||||||
{{ message }}
|
{% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-700
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
{% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-700
|
||||||
|
{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-700
|
||||||
|
{% else %}bg-blue-50 border-blue-200 text-blue-700{% endif %}">
|
||||||
|
<div class="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0
|
||||||
|
{% if message.tags == 'error' %}bg-red-100
|
||||||
|
{% elif message.tags == 'success' %}bg-green-100
|
||||||
|
{% elif message.tags == 'warning' %}bg-yellow-100
|
||||||
|
{% else %}bg-blue-100{% endif %}">
|
||||||
|
<i data-lucide="{% if message.tags == 'error' %}alert-circle
|
||||||
|
{% elif message.tags == 'success' %}check-circle
|
||||||
|
{% elif message.tags == 'warning' %}alert-triangle
|
||||||
|
{% else %}info{% endif %}"
|
||||||
|
class="w-5 h-5
|
||||||
|
{% if message.tags == 'error' %}text-red-500
|
||||||
|
{% elif message.tags == 'success' %}text-green-500
|
||||||
|
{% elif message.tags == 'warning' %}text-yellow-600
|
||||||
|
{% else %}text-blue-500{% endif %}">
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-sm font-medium">{{ message }}</p>
|
||||||
|
</div>
|
||||||
|
<button type="button"
|
||||||
|
onclick="this.parentElement.remove()"
|
||||||
|
class="text-current hover:opacity-70 transition-opacity">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<form method="post" novalidate id="source-form">
|
<!-- Form -->
|
||||||
|
<form method="post" novalidate id="source-form" class="space-y-6">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="row">
|
<!-- Name Field -->
|
||||||
<div class="col-md-6">
|
<div class="form-field">
|
||||||
<div class="mb-3">
|
<label for="{{ form.name.id_for_label }}" class="form-label flex items-center gap-2">
|
||||||
<label for="{{ form.name.id_for_label }}" class="form-label">
|
<i data-lucide="tag" class="w-4 h-4 text-gray-400"></i>
|
||||||
{{ form.name.label }} <span class="text-danger">*</span>
|
{% trans "Name" %}
|
||||||
|
{% if form.name.field.required %}<span class="text-temple-red">*</span>{% endif %}
|
||||||
</label>
|
</label>
|
||||||
{{ form.name|add_class:"form-control" }}
|
<input type="{{ form.name.field.widget.input_type }}"
|
||||||
{% if form.name.errors %}
|
name="{{ form.name.name }}"
|
||||||
<div class="invalid-feedback d-block">
|
id="{{ form.name.id_for_label }}"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="{% trans 'Enter a name for this integration source' %}"
|
||||||
|
{% if form.name.value %}value="{{ form.name.value }}"{% endif %}>
|
||||||
|
{% if form.name.help_text %}
|
||||||
|
<p class="form-helptext">{{ form.name.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
{% for error in form.name.errors %}
|
{% for error in form.name.errors %}
|
||||||
{{ error }}
|
<p class="form-error">{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.name.help_text }}</div>
|
<!-- Source Type Field -->
|
||||||
</div>
|
<div class="form-field">
|
||||||
</div>
|
<label for="{{ form.source_type.id_for_label }}" class="form-label flex items-center gap-2">
|
||||||
<div class="col-md-6">
|
<i data-lucide="server" class="w-4 h-4 text-gray-400"></i>
|
||||||
<div class="mb-3">
|
{% trans "Source Type" %}
|
||||||
<label for="{{ form.source_type.id_for_label }}" class="form-label">
|
{% if form.source_type.field.required %}<span class="text-temple-red">*</span>{% endif %}
|
||||||
{{ form.source_type.label }} <span class="text-danger">*</span>
|
|
||||||
</label>
|
</label>
|
||||||
{{ form.source_type|add_class:"form-control" }}
|
{{ form.source_type.as_widget }}
|
||||||
{% if form.source_type.errors %}
|
{% if form.source_type.help_text %}
|
||||||
<div class="invalid-feedback d-block">
|
<p class="form-helptext">{{ form.source_type.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
{% for error in form.source_type.errors %}
|
{% for error in form.source_type.errors %}
|
||||||
{{ error }}
|
<p class="form-error">{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.source_type.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<!-- IP Address Field -->
|
||||||
<div class="col-md-6">
|
<div class="form-field">
|
||||||
<div class="mb-3">
|
<label for="{{ form.ip_address.id_for_label }}" class="form-label flex items-center gap-2">
|
||||||
<label for="{{ form.ip_address.id_for_label }}" class="form-label">
|
<i data-lucide="globe" class="w-4 h-4 text-gray-400"></i>
|
||||||
{{ form.ip_address.label }} <span class="text-danger">*</span>
|
{% trans "IP Address" %}
|
||||||
|
{% if form.ip_address.field.required %}<span class="text-temple-red">*</span>{% endif %}
|
||||||
</label>
|
</label>
|
||||||
{{ form.ip_address|add_class:"form-control" }}
|
<input type="{{ form.ip_address.field.widget.input_type }}"
|
||||||
{% if form.ip_address.errors %}
|
name="{{ form.ip_address.name }}"
|
||||||
<div class="invalid-feedback d-block">
|
id="{{ form.ip_address.id_for_label }}"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="{% trans 'e.g., 192.168.1.100' %}"
|
||||||
|
{% if form.ip_address.value %}value="{{ form.ip_address.value }}"{% endif %}>
|
||||||
|
{% if form.ip_address.help_text %}
|
||||||
|
<p class="form-helptext">{{ form.ip_address.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
{% for error in form.ip_address.errors %}
|
{% for error in form.ip_address.errors %}
|
||||||
{{ error }}
|
<p class="form-error">{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.ip_address.help_text }}</div>
|
<!-- Trusted IPs Field -->
|
||||||
</div>
|
<div class="form-field">
|
||||||
</div>
|
<label for="{{ form.trusted_ips.id_for_label }}" class="form-label flex items-center gap-2">
|
||||||
<div class="col-md-6">
|
<i data-lucide="shield" class="w-4 h-4 text-gray-400"></i>
|
||||||
<div class="mb-3">
|
{% trans "Trusted IPs" %}
|
||||||
<label for="{{ form.trusted_ips.id_for_label }}" class="form-label">
|
|
||||||
{{ form.trusted_ips.label }}
|
|
||||||
</label>
|
</label>
|
||||||
{{ form.trusted_ips|add_class:"form-control" }}
|
<input type="{{ form.trusted_ips.field.widget.input_type }}"
|
||||||
{% if form.trusted_ips.errors %}
|
name="{{ form.trusted_ips.name }}"
|
||||||
<div class="invalid-feedback d-block">
|
id="{{ form.trusted_ips.id_for_label }}"
|
||||||
|
class="form-input"
|
||||||
|
placeholder="{% trans 'e.g., 192.168.1.1, 192.168.1.2 (comma-separated)' %}"
|
||||||
|
{% if form.trusted_ips.value %}value="{{ form.trusted_ips.value }}"{% endif %}>
|
||||||
|
{% if form.trusted_ips.help_text %}
|
||||||
|
<p class="form-helptext">{{ form.trusted_ips.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
{% for error in form.trusted_ips.errors %}
|
{% for error in form.trusted_ips.errors %}
|
||||||
{{ error }}
|
<p class="form-error">{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.trusted_ips.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
<!-- Description Field -->
|
||||||
<label for="{{ form.description.id_for_label }}" class="form-label">
|
<div class="form-field">
|
||||||
{{ form.description.label }}
|
<label for="{{ form.description.id_for_label }}" class="form-label flex items-center gap-2">
|
||||||
|
<i data-lucide="align-left" class="w-4 h-4 text-gray-400"></i>
|
||||||
|
{% trans "Description" %}
|
||||||
</label>
|
</label>
|
||||||
{{ form.description|add_class:"form-control" }}
|
<textarea name="{{ form.description.name }}"
|
||||||
{% if form.description.errors %}
|
id="{{ form.description.id_for_label }}"
|
||||||
<div class="invalid-feedback d-block">
|
class="form-textarea"
|
||||||
|
placeholder="{% trans 'Add a description...' %}">{% if form.description.value %}{{ form.description.value }}{% endif %}</textarea>
|
||||||
|
{% if form.description.help_text %}
|
||||||
|
<p class="form-helptext">{{ form.description.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
{% for error in form.description.errors %}
|
{% for error in form.description.errors %}
|
||||||
{{ error }}
|
<p class="form-error">{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.description.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
<!-- Is Active Field -->
|
||||||
<div class="col-md-6">
|
<div class="form-field">
|
||||||
<div class="mb-3">
|
<label for="{{ form.is_active.id_for_label }}" class="form-label flex items-center gap-2">
|
||||||
<div class="form-check">
|
<i data-lucide="power" class="w-4 h-4 text-gray-400"></i>
|
||||||
{{ form.is_active|add_class:"form-check-input" }}
|
{% trans "Active Status" %}
|
||||||
<label for="{{ form.is_active.id_for_label }}" class="form-check-label">
|
|
||||||
{{ form.is_active.label }}
|
|
||||||
</label>
|
</label>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
{{ form.is_active.as_widget }}
|
||||||
|
<span class="text-sm text-gray-600">{% trans "Enable this integration source" %}</span>
|
||||||
</div>
|
</div>
|
||||||
{% if form.is_active.errors %}
|
{% if form.is_active.help_text %}
|
||||||
<div class="invalid-feedback d-block">
|
<p class="form-helptext">{{ form.is_active.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
{% for error in form.is_active.errors %}
|
{% for error in form.is_active.errors %}
|
||||||
{{ error }}
|
<p class="form-error">{{ error }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.is_active.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- API Credentials Section -->
|
<!-- API Credentials Section (for existing sources) -->
|
||||||
{% if source %}
|
{% if source %}
|
||||||
<div class="card bg-light mb-4">
|
<div class="mt-8 p-6 bg-gray-50 rounded-2xl border border-gray-200">
|
||||||
<div class="card-header">
|
<div class="flex items-center gap-3 mb-4">
|
||||||
<h6 class="mb-0">{% trans "API Credentials" %}</h6>
|
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
||||||
|
<i data-lucide="key" class="w-5 h-5 text-temple-red"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div>
|
||||||
<div class="row">
|
<h3 class="text-lg font-bold text-gray-900">{% trans "API Credentials" %}</h3>
|
||||||
<div class="col-md-6">
|
<p class="text-sm text-gray-500">{% trans "Generated credentials for API access" %}</p>
|
||||||
<div class="mb-3">
|
</div>
|
||||||
<label class="form-label">{% trans "API Key" %}</label>
|
</div>
|
||||||
<div class="input-group">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<input type="text" class="form-control" value="{{ source.api_key }}" readonly>
|
<div>
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
<label class="block text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
||||||
|
<i data-lucide="key" class="w-4 h-4 text-gray-400"></i>
|
||||||
|
{% trans "API Key" %}
|
||||||
|
</label>
|
||||||
|
<div class="flex">
|
||||||
|
<input type="text" value="{{ source.api_key }}" readonly class="form-input rounded-r-none">
|
||||||
|
<button type="button"
|
||||||
hx-post="{% url 'copy_to_clipboard' %}"
|
hx-post="{% url 'copy_to_clipboard' %}"
|
||||||
hx-vals='{"text": "{{ source.api_key }}"}'
|
hx-vals='{"text": "{{ source.api_key }}"}'
|
||||||
title="{% trans 'Copy to clipboard' %}">
|
title="{% trans 'Copy to clipboard' %}"
|
||||||
<i class="fas fa-copy"></i>
|
class="btn-action inline-flex items-center justify-center px-3 border border-l-0 border-gray-300 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-r-lg transition">
|
||||||
|
<i data-lucide="copy" class="w-4 h-4"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div class="col-md-6">
|
<label class="block text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
||||||
<div class="mb-3">
|
<i data-lucide="lock" class="w-4 h-4 text-gray-400"></i>
|
||||||
<label class="form-label">{% trans "API Secret" %}</label>
|
{% trans "API Secret" %}
|
||||||
<div class="input-group">
|
</label>
|
||||||
<input type="password" class="form-control" value="{{ source.api_secret }}" readonly id="api-secret">
|
<div class="flex">
|
||||||
<button type="button" class="btn btn-outline-secondary" onclick="toggleSecretVisibility()">
|
<input type="password" value="{{ source.api_secret }}" readonly id="api-secret" class="form-input rounded-l-lg">
|
||||||
<i class="fas fa-eye" id="secret-toggle-icon"></i>
|
<button type="button" onclick="toggleSecretVisibility()" class="btn-action inline-flex items-center justify-center px-3 border border-l-0 border-r-0 border-gray-300 bg-gray-100 hover:bg-gray-200 text-gray-600 transition">
|
||||||
|
<i data-lucide="eye" id="secret-toggle-icon" class="w-4 h-4"></i>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-outline-secondary"
|
<button type="button"
|
||||||
hx-post="{% url 'copy_to_clipboard' %}"
|
hx-post="{% url 'copy_to_clipboard' %}"
|
||||||
hx-vals='{"text": "{{ source.api_secret }}"}'
|
hx-vals='{"text": "{{ source.api_secret }}"}'
|
||||||
title="{% trans 'Copy to clipboard' %}">
|
title="{% trans 'Copy to clipboard' %}"
|
||||||
<i class="fas fa-copy"></i>
|
class="btn-action inline-flex items-center justify-center px-3 border border-l-0 border-gray-300 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded-r-lg transition">
|
||||||
|
<i data-lucide="copy" class="w-4 h-4"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="mt-4">
|
||||||
<div class="text-end">
|
<a href="{% url 'generate_api_keys' source.pk %}"
|
||||||
<a href="{% url 'generate_api_keys' source.pk %}" class="btn btn-warning">
|
class="btn-action inline-flex items-center gap-2 px-5 py-3 bg-yellow-500 text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
|
||||||
<i class="fas fa-key"></i> {% trans "Generate New Keys" %}
|
<i data-lucide="refresh-cw" class="w-5 h-5"></i>
|
||||||
|
{% trans "Generate New Keys" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
<!-- Form Actions -->
|
||||||
<button form="source-form" type="submit" class="btn btn-main-action">
|
<div class="flex flex-col sm:flex-row gap-3 pt-6 border-t border-gray-200 mt-6">
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Save" %}
|
<button form="source-form" type="submit"
|
||||||
|
class="btn-primary flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py-3 bg-temple-red text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
|
||||||
|
<i data-lucide="save" class="w-5 h-5"></i>
|
||||||
|
{% trans "Save" %}
|
||||||
</button>
|
</button>
|
||||||
|
<a href="{% url 'source_list' %}"
|
||||||
|
class="btn-secondary flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
|
// Initialize Lucide icons
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Auto-adjust textarea height
|
||||||
|
const textarea = document.querySelector('textarea[name="description"]');
|
||||||
|
if (textarea) {
|
||||||
|
textarea.addEventListener('input', function() {
|
||||||
|
this.style.height = 'auto';
|
||||||
|
this.style.height = this.scrollHeight + 'px';
|
||||||
|
});
|
||||||
|
// Set initial height
|
||||||
|
textarea.style.height = textarea.scrollHeight + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply Tailwind classes to source_type select
|
||||||
|
const sourceTypeSelect = document.querySelector('select[name="source_type"]');
|
||||||
|
if (sourceTypeSelect) {
|
||||||
|
sourceTypeSelect.classList.add('form-select');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply Tailwind classes to is_active checkbox
|
||||||
|
const isActiveCheckbox = document.querySelector('input[name="is_active"]');
|
||||||
|
if (isActiveCheckbox) {
|
||||||
|
isActiveCheckbox.classList.add('w-5', 'h-5', 'text-temple-red', 'rounded', 'border-gray-300', 'focus:ring-temple-red', 'focus:border-temple-red');
|
||||||
|
}
|
||||||
|
|
||||||
// Form Validation
|
// Form Validation
|
||||||
const form = document.getElementById('source-form');
|
const form = document.getElementById('source-form');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener('submit', function(e) {
|
||||||
const submitBtn = form.querySelector('button[type="submit"]');
|
const submitBtn = form.querySelector('button[type="submit"]');
|
||||||
submitBtn.classList.add('loading');
|
|
||||||
submitBtn.disabled = true;
|
|
||||||
|
|
||||||
// Basic validation
|
// Basic validation
|
||||||
const name = document.getElementById('id_name');
|
const name = document.getElementById('id_name');
|
||||||
if (name && !name.value.trim()) {
|
if (name && !name.value.trim()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitBtn.classList.remove('loading');
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
alert('{% trans "Source name is required." %}');
|
alert('{% trans "Source name is required." %}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -435,20 +558,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const ipAddress = document.getElementById('id_ip_address');
|
const ipAddress = document.getElementById('id_ip_address');
|
||||||
if (ipAddress && ipAddress.value.trim() && !isValidIP(ipAddress.value.trim())) {
|
if (ipAddress && ipAddress.value.trim() && !isValidIP(ipAddress.value.trim())) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
submitBtn.classList.remove('loading');
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
alert('{% trans "Please enter a valid IP address." %}');
|
alert('{% trans "Please enter a valid IP address." %}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP validation helper
|
|
||||||
function isValidIP(ip) {
|
|
||||||
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
|
||||||
return ipRegex.test(ip);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn before leaving if changes are made
|
// Warn before leaving if changes are made
|
||||||
let formChanged = false;
|
let formChanged = false;
|
||||||
const formInputs = form ? form.querySelectorAll('input, select, textarea') : [];
|
const formInputs = form ? form.querySelectorAll('input, select, textarea') : [];
|
||||||
@ -474,34 +589,42 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Toggle Secret Visibility
|
||||||
function toggleSecretVisibility() {
|
function toggleSecretVisibility() {
|
||||||
const secretInput = document.getElementById('api-secret');
|
const secretInput = document.getElementById('api-secret');
|
||||||
const toggleIcon = document.getElementById('secret-toggle-icon');
|
const toggleIcon = document.getElementById('secret-toggle-icon');
|
||||||
|
|
||||||
if (secretInput.type === 'password') {
|
if (secretInput.type === 'password') {
|
||||||
secretInput.type = 'text';
|
secretInput.type = 'text';
|
||||||
toggleIcon.classList.remove('fa-eye');
|
toggleIcon.setAttribute('data-lucide', 'eye-off');
|
||||||
toggleIcon.classList.add('fa-eye-slash');
|
|
||||||
} else {
|
} else {
|
||||||
secretInput.type = 'password';
|
secretInput.type = 'password';
|
||||||
toggleIcon.classList.remove('fa-eye-slash');
|
toggleIcon.setAttribute('data-lucide', 'eye');
|
||||||
toggleIcon.classList.add('fa-eye');
|
|
||||||
}
|
}
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
|
||||||
|
// IP validation helper
|
||||||
|
function isValidIP(ip) {
|
||||||
|
const ipRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||||
|
return ipRegex.test(ip);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle HTMX copy to clipboard feedback
|
// Handle HTMX copy to clipboard feedback
|
||||||
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
||||||
if (evt.detail.successful && evt.detail.target.matches('[hx-post*="copy_to_clipboard"]')) {
|
if (evt.detail.successful && evt.detail.target.matches('[hx-post*="copy_to_clipboard"]')) {
|
||||||
const button = evt.detail.target;
|
const button = evt.detail.target;
|
||||||
const originalIcon = button.innerHTML;
|
const originalHTML = button.innerHTML;
|
||||||
button.innerHTML = '<i class="fas fa-check"></i>';
|
button.innerHTML = '<i data-lucide="check" class="w-4 h-4"></i>';
|
||||||
button.classList.remove('btn-outline-secondary');
|
button.classList.remove('bg-gray-100', 'hover:bg-gray-200', 'text-gray-600');
|
||||||
button.classList.add('btn-success');
|
button.classList.add('bg-green-500', 'text-white');
|
||||||
|
lucide.createIcons();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
button.innerHTML = originalIcon;
|
button.innerHTML = originalHTML;
|
||||||
button.classList.remove('btn-success');
|
button.classList.remove('bg-green-500', 'text-white');
|
||||||
button.classList.add('btn-outline-secondary');
|
button.classList.add('bg-gray-100', 'hover:bg-gray-200', 'text-gray-600');
|
||||||
|
lucide.createIcons();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,166 +1,269 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block title %}{% trans "Sources" %}{% endblock %}
|
{% block title %}{% trans "Sources" %} - University ATS{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="space-y-6">
|
||||||
|
|
||||||
<div class="container-fluid">
|
<!-- Mobile Header -->
|
||||||
<nav aria-label="breadcrumb">
|
<div class="lg:hidden mb-4">
|
||||||
<ol class="breadcrumb">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li>
|
<h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
<li class="breadcrumb-item active" aria-current="page" style="
|
<div class="bg-temple-red/10 p-2 rounded-lg">
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
<i data-lucide="database" class="w-6 h-6 text-temple-red"></i>
|
||||||
font-weight: 600;
|
</div>
|
||||||
">{% trans "Sources Settings" %}</li>
|
{% trans "Integration Sources" %}
|
||||||
</ol>
|
</h1>
|
||||||
</nav>
|
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl text-sm transition shadow-sm">
|
||||||
<div class="row">
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add Source" %}
|
||||||
<div class="col-12">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1 class="h3 mb-0">{% trans "Integration Sources" %}</h1>
|
|
||||||
<a href="{% url 'source_create' %}" class="btn btn-main-action">
|
|
||||||
{% trans "Create Source for Integration" %} <i class="fas fa-plus"></i>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search and Filters -->
|
<!-- Mobile Filters -->
|
||||||
<div class="card mb-4">
|
<div class="space-y-3">
|
||||||
<div class="card-body">
|
<form method="get" class="flex gap-2">
|
||||||
<form method="get" class="row g-3">
|
<div class="flex-1">
|
||||||
<div class="col-md-8">
|
<div class="relative">
|
||||||
<div class="input-group">
|
<i data-lucide="search" class="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2"></i>
|
||||||
<span class="input-group-text">
|
<input type="text" name="q"
|
||||||
<i class="fas fa-search"></i>
|
placeholder="{% trans 'Search sources...' %}"
|
||||||
</span>
|
value="{{ search_query }}"
|
||||||
<input type="text" class="form-control" name="q"
|
class="w-full pl-10 pr-4 py-2.5 bg-white border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
placeholder="Search sources..." value="{{ search_query }}">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<button type="submit" class="bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2.5 rounded-xl transition">
|
||||||
<button type="submit" class="btn btn-outline-primary">
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-search"></i> {% trans "Search" %}
|
|
||||||
</button>
|
</button>
|
||||||
{% if search_query %}
|
{% if search_query %}
|
||||||
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'source_list' %}" class="bg-gray-100 hover:bg-gray-200 text-gray-600 px-4 py-2.5 rounded-xl transition">
|
||||||
<i class="fas fa-times"></i> {% trans "Clear" %}
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop Header -->
|
||||||
|
<div class="hidden lg:block">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
|
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||||
|
<li><a href="{% url 'settings' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
|
||||||
|
<i data-lucide="settings" class="w-4 h-4"></i> {% trans "Settings" %}
|
||||||
|
</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li class="text-temple-red font-semibold">{% trans "Sources Settings" %}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="database" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Integration Sources" %}
|
||||||
|
</h1>
|
||||||
|
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create New Source" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop Filters -->
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
|
<i data-lucide="filter" class="w-5 h-5"></i>
|
||||||
|
{% trans "Search Sources" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
|
<form method="get" class="flex gap-3">
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="relative">
|
||||||
|
<i data-lucide="search" class="w-5 h-5 text-gray-400 absolute left-4 top-1/2 -translate-y-1/2"></i>
|
||||||
|
<input type="text" name="q"
|
||||||
|
placeholder="{% trans 'Search by name or type...' %}"
|
||||||
|
value="{{ search_query }}"
|
||||||
|
class="w-full pl-11 pr-4 py-2.5 bg-white border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-5 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i> {% trans "Search" %}
|
||||||
|
</button>
|
||||||
|
{% if search_query %}
|
||||||
|
<a href="{% url 'source_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i> {% trans "Clear" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Results Summary -->
|
<!-- Results Summary -->
|
||||||
{% if search_query %}
|
{% if search_query %}
|
||||||
<div class="alert alert-info">
|
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4 flex items-center gap-3">
|
||||||
Found {{ total_sources }} source{{ total_sources|pluralize }} matching "{{ search_query }}"
|
<i data-lucide="info" class="w-5 h-5 text-blue-600"></i>
|
||||||
|
<p class="text-sm text-blue-800">
|
||||||
|
{% blocktrans %}Found {{ count }} source{{ count|pluralize }} matching "{{ search_query }}"{% endblocktrans %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Sources Table -->
|
{# --- MOBILE CARD VIEW --- #}
|
||||||
<div class="card">
|
<div class="lg:hidden grid grid-cols-1 gap-4">
|
||||||
<div class="card-body">
|
{% for source in page_obj %}
|
||||||
{% if page_obj %}
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="table-responsive">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<table class="table table-hover">
|
<div class="flex justify-between items-start">
|
||||||
<thead class="table-light">
|
<h5 class="font-bold text-lg">
|
||||||
|
<a href="{% url 'source_detail' source.pk %}" class="hover:text-white/80 transition-colors">{{ source.name }}</a>
|
||||||
|
</h5>
|
||||||
|
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2.5 py-1 rounded-full bg-white/20 backdrop-blur-sm border border-white/30">
|
||||||
|
{{ source.source_type }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-white/80 mt-1">ID: {{ source.pk }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 space-y-3">
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<i data-lucide="key" class="w-4 h-4 text-temple-red"></i>
|
||||||
|
<code class="text-xs bg-gray-100 px-2 py-1 rounded">{{ source.api_key|truncatechars:20 }}</code>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<i data-lucide="calendar" class="w-4 h-4 text-temple-red"></i>
|
||||||
|
<span>{% trans "Created" %}: {{ source.created_at|date:"M d, Y" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-sm">
|
||||||
|
<i data-lucide="activity" class="w-4 h-4 text-temple-red"></i>
|
||||||
|
{% if source.is_active %}
|
||||||
|
<span class="text-emerald-600 font-semibold">{% trans "Active" %}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="text-gray-500 font-semibold">{% trans "Inactive" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex gap-2 pt-3 border-t border-gray-100">
|
||||||
|
<a href="{% url 'source_detail' source.pk %}" class="flex-1 inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Details" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'source_update' source.pk %}" class="inline-flex items-center justify-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-50 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden text-center p-8">
|
||||||
|
<i data-lucide="database" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No sources found" %}</h3>
|
||||||
|
<p class="text-gray-500 mb-4">
|
||||||
|
{% if search_query %}
|
||||||
|
{% blocktrans %}No sources match your search criteria "{{ search_query }}".{% endblocktrans %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Get started by creating your first source." %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-5 py-2.5 rounded-xl transition shadow-sm">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create Source" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# --- DESKTOP TABLE VIEW --- #}
|
||||||
|
<div class="hidden lg:block bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
|
<i data-lucide="list" class="w-5 h-5"></i>
|
||||||
|
{% trans "Source Listings" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th>{% trans "Name" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Name" %}</th>
|
||||||
<th>{% trans "Type" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Type" %}</th>
|
||||||
<th>{% trans "Status" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Status" %}</th>
|
||||||
<th>{% trans "API Key" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "API Key" %}</th>
|
||||||
<th>{% trans "Created" %}</th>
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Created" %}</th>
|
||||||
<th>{% trans "Actions" %}</th>
|
<th scope="col" class="px-6 py-3 text-center text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody class="bg-white divide-y divide-gray-100">
|
||||||
|
|
||||||
{% for source in page_obj %}
|
{% for source in page_obj %}
|
||||||
<tr>
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
<td>
|
<td class="px-6 py-4">
|
||||||
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none text-primary-theme">
|
<a href="{% url 'source_detail' source.pk %}" class="text-temple-red font-semibold hover:text-[#7a1a29] transition-colors">{{ source.name }}</a>
|
||||||
<strong>{{ source.name }}</strong>
|
<br>
|
||||||
</a>
|
<span class="text-xs text-gray-500">ID: {{ source.pk }}</span>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-6 py-4">
|
||||||
<span class="badge bg-primary-theme">{{ source.source_type }}</span>
|
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2.5 py-1 rounded-full bg-temple-red/10 text-temple-red border border-temple-red">
|
||||||
|
{{ source.source_type }}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-6 py-4">
|
||||||
{% if source.is_active %}
|
{% if source.is_active %}
|
||||||
<span class="badge bg-primary-theme">{% trans "Active" %}</span>
|
<span class="inline-flex items-center gap-1.5 text-sm font-semibold text-emerald-600">
|
||||||
|
<i data-lucide="check-circle-2" class="w-4 h-4"></i> {% trans "Active" %}
|
||||||
|
</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="badge bg-primary-theme">{% trans "Inactive" %}</span>
|
<span class="inline-flex items-center gap-1.5 text-sm font-semibold text-gray-500">
|
||||||
|
<i data-lucide="x-circle" class="w-4 h-4"></i> {% trans "Inactive" %}
|
||||||
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-6 py-4">
|
||||||
<code class="small text-primary-theme">{{ source.api_key|truncatechars:20 }}</code>
|
<code class="text-xs bg-gray-100 px-2 py-1 rounded text-temple-red">{{ source.api_key|truncatechars:20 }}</code>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td class="px-6 py-4 text-sm text-gray-700">{{ source.created_at|date:"M d, Y" }}</td>
|
||||||
<small class="text-muted">{{ source.created_at|date:"M d, Y" }}</small>
|
<td class="px-6 py-4">
|
||||||
</td>
|
<div class="flex items-center gap-2 justify-center">
|
||||||
<td>
|
<a href="{% url 'source_detail' source.pk %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-temple-red hover:bg-[#7a1a29] text-white transition" title="{% trans 'View' %}">
|
||||||
<div class="btn-group" role="group">
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
<a href="{% url 'source_detail' source.pk %}"
|
|
||||||
class="btn btn-sm btn-outline-primary" title="View">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'source_update' source.pk %}"
|
<a href="{% url 'source_update' source.pk %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-600 transition" title="{% trans 'Edit' %}">
|
||||||
class="btn btn-sm btn-outline-secondary" title="Edit">
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</a>
|
</a>
|
||||||
{% comment %} <button type="button"
|
|
||||||
class="btn btn-sm btn-outline-warning"
|
|
||||||
hx-post="{% url 'toggle_source_status' source.pk %}"
|
|
||||||
hx-confirm="Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?"
|
|
||||||
title="{{ source.is_active|yesno:'Deactivate,Activate' }}">
|
|
||||||
<i class="fas fa-{{ source.is_active|yesno:'pause,play' }}"></i>
|
|
||||||
</button> {% endcomment %}
|
|
||||||
{% comment %} <a href="{% url 'source_delete' source.pk %}"
|
|
||||||
class="btn btn-sm btn-outline-danger" title="Delete">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</a> {% endcomment %}
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="px-6 py-12 text-center">
|
||||||
|
<i data-lucide="database" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No sources found" %}</h3>
|
||||||
|
<p class="text-gray-500 mb-4">
|
||||||
|
{% if search_query %}
|
||||||
|
{% blocktrans %}No sources match your search criteria "{{ search_query }}".{% endblocktrans %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Get started by creating your first source." %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-5 py-2.5 rounded-xl transition shadow-sm">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create Source" %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Pagination -->
|
|
||||||
|
|
||||||
{% include "includes/paginator.html" %}
|
{% include "includes/paginator.html" %}
|
||||||
{% else %}
|
|
||||||
<div class="text-center py-5">
|
|
||||||
<i class="fas fa-database fa-3x text-muted mb-3"></i>
|
|
||||||
<h5 class="text-muted">{% trans "No sources found" %}</h5>
|
|
||||||
<p class="text-muted">
|
|
||||||
{% if search_query %}
|
|
||||||
{% blocktrans with query=query %}No sources match your search criteria "{{ query }}".{% endblocktrans %}
|
|
||||||
{% else %}
|
|
||||||
{% trans "Get started by creating your first source." %}
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
<a href="{% url 'source_create' %}" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-plus"></i> {% trans "Create Source" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
<script>
|
||||||
// Auto-refresh after status toggle
|
lucide.createIcons();
|
||||||
|
|
||||||
|
// Auto-refresh after status toggle (for HTMX)
|
||||||
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
||||||
if (evt.detail.successful) {
|
if (evt.detail.successful) {
|
||||||
// Reload the page after a short delay to show updated status
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user