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;
|
||||||
position: fixed;
|
color: var(--gray-600);
|
||||||
bottom: 0;
|
margin-top: 1rem;
|
||||||
left: 0;
|
text-align: center;
|
||||||
right: 0;
|
}
|
||||||
background: white;
|
|
||||||
|
.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;
|
padding: 1rem;
|
||||||
border-top: 1px solid var(--border);
|
background: var(--gray-50);
|
||||||
box-shadow: 0 -4px 6px -1px rgba(0,0,0,0.1);
|
border-radius: 0.5rem;
|
||||||
z-index: 1000;
|
font-weight: 600;
|
||||||
|
color: var(--gray-600);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 993px) { .mobile-apply-bar { display: none; } }
|
/* Info Card */
|
||||||
|
.info-title {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: var(--gray-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--gray-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Apply */
|
||||||
|
.mobile-apply {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
.mobile-apply {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #ffffff;
|
||||||
|
border-top: 1px solid var(--gray-200);
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-apply-btn {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--red);
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding-bottom: 5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icons */
|
||||||
|
.icon-sm {
|
||||||
|
width: 1.125rem;
|
||||||
|
height: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-md {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.job-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.details-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
padding: 1.5rem;
|
||||||
|
gap: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
padding: 2rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</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;
|
|
||||||
}
|
}
|
||||||
|
</script>
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
||||||
background-color: var(--light-bg);
|
|
||||||
color: var(--temple-dark);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- NAVIGATION --- */
|
|
||||||
.navbar {
|
|
||||||
height: var(--nav-height);
|
|
||||||
background: var(--white);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding-inline: 2rem;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-brand {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
text-decoration: none;
|
|
||||||
gap: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--temple-red);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-brand-icon {
|
|
||||||
width: 45px;
|
|
||||||
height: 45px;
|
|
||||||
background: var(--temple-red);
|
|
||||||
color: white;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link {
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--gray-text);
|
|
||||||
font-weight: 500;
|
|
||||||
transition: color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link:hover { color: var(--temple-red); }
|
|
||||||
|
|
||||||
/* --- BUTTONS --- */
|
|
||||||
.btn-lang {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--temple-red);
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-lang:hover { background: var(--temple-cream); }
|
|
||||||
|
|
||||||
/* --- DROPDOWN --- */
|
|
||||||
.dropdown { position: relative; }
|
|
||||||
|
|
||||||
.dropdown-trigger {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 2px solid var(--temple-cream);
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-avatar-placeholder {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--temple-red);
|
|
||||||
color: white;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-menu {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + 10px);
|
|
||||||
inset-inline-end: 0;
|
|
||||||
background: var(--white);
|
|
||||||
min-width: 260px;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
overflow: hidden;
|
|
||||||
z-index: 1100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown.active .dropdown-menu { display: block; }
|
|
||||||
|
|
||||||
.dropdown-header {
|
|
||||||
padding: 1rem;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #444;
|
|
||||||
gap: 0.75rem;
|
|
||||||
transition: background 0.2s;
|
|
||||||
border: none;
|
|
||||||
background: none;
|
|
||||||
width: 100%;
|
|
||||||
text-align: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-item:hover { background: var(--temple-cream); }
|
|
||||||
.dropdown-item svg { width: 20px; height: 20px; color: var(--gray-text); }
|
|
||||||
.dropdown-item.logout { color: var(--danger); }
|
|
||||||
.dropdown-item.logout svg { color: var(--danger); }
|
|
||||||
|
|
||||||
/* --- ALERTS --- */
|
|
||||||
.alert-container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 1rem auto;
|
|
||||||
padding-inline: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert {
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background: #fef2f2;
|
|
||||||
color: var(--temple-red-dark);
|
|
||||||
border-inline-start: 4px solid var(--temple-red);
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heroicon { width: 20px; height: 20px; flex-shrink: 0; }
|
|
||||||
|
|
||||||
/* --- RESPONSIVE --- */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.navbar { padding-inline: 1rem; }
|
|
||||||
.nav-brand span { display: none; }
|
|
||||||
.d-mobile-none { display: none; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</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">
|
||||||
</h1>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
<a href="{% url 'form_templates_list' %}" class="btn btn-secondary btn-sm">
|
<i data-lucide="file-plus-2" class="w-8 h-8 text-temple-red"></i>
|
||||||
<i class="fas fa-arrow-left me-1"></i>Back to Templates
|
</div>
|
||||||
|
Create Form Template
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> Back to Templates
|
||||||
</a>
|
</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,207 +65,121 @@
|
|||||||
.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="bg-white rounded-2xl shadow-md p-6 mb-6">
|
||||||
<div class="embed-info">
|
<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-start mb-4">
|
<div class="flex-1">
|
||||||
<div>
|
<h1 class="text-2xl md:text-3xl font-bold text-gray-900 mb-2 flex items-center gap-2">
|
||||||
<h1 class="h2 mb-2">
|
<i data-lucide="code-2" class="w-7 h-7 text-temple-red"></i>
|
||||||
<i class="fas fa-code text-primary"></i> Embed Form
|
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>
|
|
||||||
<a href="{% url 'form_preview' form.id %}" target="_blank" class="btn btn-outline-primary">
|
|
||||||
<i class="fas fa-external-link-alt"></i> Preview Form
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
<a href="{% url 'form_preview' form.id %}" target="_blank" class="inline-flex items-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-4 py-2.5 rounded-xl transition font-medium">
|
||||||
|
<i data-lucide="external-link" class="w-4 h-4"></i> Preview Form
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Stats -->
|
<!-- 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="col-md-3">
|
|
||||||
<div class="p-3">
|
|
||||||
<h4 class="text-success mb-1">{{ form.structure.wizards|length|default:0 }}</h4>
|
|
||||||
<small class="text-muted">Steps</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="p-3">
|
|
||||||
<h4 class="text-info mb-1">
|
|
||||||
{% if form.structure.wizards %}
|
|
||||||
{{ form.structure.wizards.0.fields|length|default:0 }}
|
|
||||||
{% else %}
|
|
||||||
0
|
|
||||||
{% endif %}
|
|
||||||
</h4>
|
|
||||||
<small class="text-muted">Fields</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-3">
|
|
||||||
<div class="p-3">
|
|
||||||
<h4 class="text-warning mb-1">{{ form.created_at|date:"M d" }}</h4>
|
|
||||||
<small class="text-muted">Created</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-4 bg-gray-50 rounded-xl">
|
||||||
|
<div class="text-2xl font-bold text-emerald-600">{{ form.structure.wizards|length|default:0 }}</div>
|
||||||
|
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Steps</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 bg-gray-50 rounded-xl">
|
||||||
|
<div class="text-2xl font-bold text-blue-600">
|
||||||
|
{% if form.structure.wizards %}
|
||||||
|
{{ form.structure.wizards.0.fields|length|default:0 }}
|
||||||
|
{% else %}
|
||||||
|
0
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Fields</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 bg-gray-50 rounded-xl">
|
||||||
|
<div class="text-2xl font-bold text-amber-600">{{ form.created_at|date:"M d" }}</div>
|
||||||
|
<div class="text-xs uppercase text-gray-500 font-semibold mt-1">Created</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Tabs -->
|
<!-- 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')">
|
||||||
</button>
|
<i data-lucide="globe" class="w-4 h-4"></i> iFrame
|
||||||
</li>
|
</button>
|
||||||
<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"
|
||||||
<button class="nav-link" id="popup-tab" data-bs-toggle="tab" data-bs-target="#popup" type="button" role="tab">
|
data-tab="popup"
|
||||||
<i class="fas fa-external-link-alt"></i> Popup
|
onclick="switchTab('popup')">
|
||||||
</button>
|
<i data-lucide="external-link" class="w-4 h-4"></i> Popup
|
||||||
</li>
|
</button>
|
||||||
<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"
|
||||||
<button class="nav-link" id="inline-tab" data-bs-toggle="tab" data-bs-target="#inline" type="button" role="tab">
|
data-tab="inline"
|
||||||
<i class="fas fa-code"></i> Inline
|
onclick="switchTab('inline')">
|
||||||
</button>
|
<i data-lucide="code" class="w-4 h-4"></i> Inline
|
||||||
</li>
|
</button>
|
||||||
</ul>
|
</div>
|
||||||
|
|
||||||
<div class="tab-content" id="embedTabsContent">
|
<!-- Tab Contents -->
|
||||||
<!-- iFrame Tab -->
|
<div id="tab-content">
|
||||||
<div class="tab-pane fade show active" id="iframe" role="tabpanel">
|
<!-- iFrame Tab -->
|
||||||
<h5 class="mb-3">iFrame Embed Code</h5>
|
<div class="tab-pane" id="iframe-pane">
|
||||||
<p class="text-muted">Embed this form directly into your website using an iframe.</p>
|
<h5 class="text-lg font-semibold text-gray-900 mb-3">iFrame Embed Code</h5>
|
||||||
|
<p class="text-gray-600 mb-4">Embed this form directly into your website using an iframe.</p>
|
||||||
|
|
||||||
<div class="code-block">
|
<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%"
|
||||||
height="600"
|
height="600"
|
||||||
frameborder="0"
|
frameborder="0"
|
||||||
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>
|
<div class="text-sm text-gray-500">Best for most websites</div>
|
||||||
<div class="responsive-option" onclick="selectResponsive(this, 'iframe', 'responsive')">
|
</div>
|
||||||
<strong>Responsive:</strong> Auto height
|
<div class="responsive-option bg-white border-2 border-gray-200 p-4 rounded-xl cursor-pointer hover:border-temple-red transition"
|
||||||
<div class="text-muted small">Adjusts to content height</div>
|
onclick="selectResponsive(this, 'iframe', 'responsive')">
|
||||||
</div>
|
<div class="font-semibold">Responsive: Auto height</div>
|
||||||
<div class="responsive-option" onclick="selectResponsive(this, 'iframe', 'full')">
|
<div class="text-sm text-gray-500">Adjusts to content height</div>
|
||||||
<strong>Full Screen:</strong> 100vh
|
</div>
|
||||||
<div class="text-muted small">Takes full viewport height</div>
|
<div class="responsive-option bg-white border-2 border-gray-200 p-4 rounded-xl cursor-pointer hover:border-temple-red transition"
|
||||||
</div>
|
onclick="selectResponsive(this, 'iframe', 'full')">
|
||||||
|
<div class="font-semibold">Full Screen: 100vh</div>
|
||||||
|
<div class="text-sm text-gray-500">Takes full viewport height</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
@ -317,107 +202,143 @@ 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">
|
||||||
</ul>
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Trigger on page load or scroll
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Custom modal dimensions
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Close on outside click
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inline Tab -->
|
||||||
|
<div class="tab-pane hidden" id="inline-pane">
|
||||||
|
<h5 class="text-lg font-semibold text-gray-900 mb-3">Inline Embed Code</h5>
|
||||||
|
<p class="text-gray-600 mb-4">Embed form HTML directly into your page for maximum customization.</p>
|
||||||
|
|
||||||
|
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4 mb-4 flex items-start gap-3">
|
||||||
|
<i data-lucide="info" class="w-5 h-5 text-blue-600 shrink-0 mt-0.5"></i>
|
||||||
|
<div>
|
||||||
|
<strong class="text-blue-900">Note:</strong> This option requires more technical knowledge but offers best integration.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Inline Tab -->
|
<div class="code-block">
|
||||||
<div class="tab-pane fade" id="inline" role="tabpanel">
|
<button class="copy-btn" onclick="copyToClipboard(this, 'inline-code')">
|
||||||
<h5 class="mb-3">Inline Embed Code</h5>
|
<i data-lucide="copy" class="w-3 h-3"></i> Copy
|
||||||
<p class="text-muted">Embed the form HTML directly into your page for maximum customization.</p>
|
</button>
|
||||||
|
<code id="inline-code"><!-- Form CSS -->
|
||||||
<div class="alert alert-info">
|
<link href="https://cdn.tailwindcss.com" rel="stylesheet">
|
||||||
<i class="fas fa-info-circle"></i> <strong>Note:</strong> This option requires more technical knowledge but offers the best integration.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="code-block">
|
|
||||||
<button class="copy-btn" onclick="copyToClipboard(this, 'inline-code')">
|
|
||||||
<i class="fas fa-copy"></i> Copy
|
|
||||||
</button>
|
|
||||||
<code id="inline-code"><!-- Form CSS -->
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- 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
|
||||||
})
|
})
|
||||||
.catch(error => console.error('Error loading form:', error));
|
.catch(error => console.error('Error loading form:', error));
|
||||||
</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">
|
||||||
</ul>
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Better SEO integration
|
||||||
</div>
|
</li>
|
||||||
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Faster initial load
|
||||||
|
</li>
|
||||||
|
<li class="flex items-center gap-2 text-gray-700">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 text-emerald-500"></i> Custom form handling
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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"
|
||||||
frameborder="0">
|
style="height: 600px;"
|
||||||
</iframe>
|
frameborder="0">
|
||||||
</div>
|
</iframe>
|
||||||
</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,140 +3,143 @@
|
|||||||
{% 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
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search and Filter -->
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-body">
|
|
||||||
<form method="get" class="row g-3">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" class="form-control" name="search" placeholder="Search forms..." value="{{ request.GET.search }}">
|
|
||||||
<button class="btn btn-outline-secondary" type="submit">
|
|
||||||
<i class="fas fa-search"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<select class="form-select" name="sort">
|
|
||||||
<option value="-created_at">Latest First</option>
|
|
||||||
<option value="created_at">Oldest First</option>
|
|
||||||
<option value="title">Title (A-Z)</option>
|
|
||||||
<option value="-title">Title (Z-A)</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
Forms
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<a href="{% url 'form_builder' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="plus" class="w-5 h-5"></i> Create New Form
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Forms List -->
|
<!-- Search and Filter -->
|
||||||
{% if page_obj %}
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 mb-6">
|
||||||
<div class="row">
|
<div class="p-6">
|
||||||
{% for form in page_obj %}
|
<form method="get" class="flex flex-col md:flex-row gap-4">
|
||||||
<div class="col-lg-4 col-md-6 mb-4">
|
<div class="flex-1">
|
||||||
<div class="card h-100">
|
<div class="relative">
|
||||||
<div class="card-body">
|
<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 }}">
|
||||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
<button class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-temple-red transition" type="submit">
|
||||||
<h5 class="card-title mb-1">{{ form.title }}</h5>
|
<i data-lucide="search" class="w-5 h-5"></i>
|
||||||
<span class="badge bg-success">Active</span>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="card-text text-muted small">
|
|
||||||
{{ form.description|truncatewords:15 }}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="mb-2">
|
|
||||||
<small class="text-muted">
|
|
||||||
<i class="fas fa-user"></i> {{ form.created_by.username }}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-2">
|
|
||||||
<small class="text-muted">
|
|
||||||
<i class="fas fa-calendar"></i> {{ form.created_at|date:"M d, Y" }}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<small class="text-muted">
|
|
||||||
<i class="fas fa-chart-bar"></i> {{ form.submissions.count }} submissions
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer bg-transparent">
|
|
||||||
<div class="btn-group w-100" role="group">
|
|
||||||
{% if form.created_by == user %}
|
|
||||||
<a href="{% url 'edit_form' form.slug %}" class="btn btn-sm btn-outline-warning">
|
|
||||||
<i class="fas fa-edit"></i> Edit
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'form_preview' form.slug %}" class="btn btn-sm btn-outline-primary" target="_blank">
|
|
||||||
<i class="fas fa-eye"></i> Preview
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'form_embed' form.slug %}" class="btn btn-sm btn-outline-secondary" target="_blank">
|
|
||||||
<i class="fas fa-code"></i> Embed
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'form_submissions' form.slug %}" class="btn btn-sm btn-outline-info">
|
|
||||||
<i class="fas fa-list"></i> Submissions
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="md:w-64">
|
||||||
<!-- Pagination -->
|
<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">
|
||||||
{% if page_obj.has_other_pages %}
|
<option value="-created_at">Latest First</option>
|
||||||
<nav aria-label="Forms pagination">
|
<option value="created_at">Oldest First</option>
|
||||||
<ul class="pagination justify-content-center">
|
<option value="title">Title (A-Z)</option>
|
||||||
{% if page_obj.has_previous %}
|
<option value="-title">Title (Z-A)</option>
|
||||||
<li class="page-item">
|
</select>
|
||||||
<a class="page-link" href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">First</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Previous</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<li class="page-item active">
|
|
||||||
<span class="page-link">{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Next</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}">Last</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<div class="text-center py-5">
|
|
||||||
<i class="fas fa-wpforms fa-3x text-muted mb-3"></i>
|
|
||||||
<h3>No forms found</h3>
|
|
||||||
<p class="text-muted">Create your first form to get started.</p>
|
|
||||||
<a href="{% url 'form_builder' %}" class="btn btn-primary">Create Form</a>
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
<!-- Forms List -->
|
||||||
|
{% if page_obj %}
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{% for form in page_obj %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 hover:shadow-md transition-shadow">
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex justify-between items-start mb-3">
|
||||||
|
<h5 class="text-lg font-bold text-gray-900 mb-1">{{ form.title }}</h5>
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-bold uppercase bg-emerald-100 text-emerald-800">Active</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-sm text-gray-600 mb-4 line-clamp-2">
|
||||||
|
{{ form.description|truncatewords:15 }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="space-y-2 mb-4">
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<i data-lucide="user" class="w-4 h-4 text-gray-400"></i>
|
||||||
|
{{ form.created_by.username }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<i data-lucide="calendar" class="w-4 h-4 text-gray-400"></i>
|
||||||
|
{{ form.created_at|date:"M d, Y" }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<i data-lucide="bar-chart-3" class="w-4 h-4 text-gray-400"></i>
|
||||||
|
{{ form.submissions.count }} submissions
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="border-t border-gray-100 p-4 bg-gray-50/50">
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{% if form.created_by == user %}
|
||||||
|
<a href="{% url 'edit_form' form.slug %}" class="inline-flex items-center gap-1 border border-amber-300 text-amber-700 hover:bg-amber-50 px-3 py-2 rounded-lg text-xs font-semibold transition">
|
||||||
|
<i data-lucide="edit-3" class="w-3 h-3"></i> Edit
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'form_preview' form.slug %}" class="inline-flex items-center gap-1 border border-temple-red text-temple-red hover:bg-temple-red/10 px-3 py-2 rounded-lg text-xs font-semibold transition" target="_blank">
|
||||||
|
<i data-lucide="eye" class="w-3 h-3"></i> Preview
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'form_embed' form.slug %}" class="inline-flex items-center gap-1 border border-gray-300 text-gray-600 hover:bg-gray-100 px-3 py-2 rounded-lg text-xs font-semibold transition" target="_blank">
|
||||||
|
<i data-lucide="code-2" class="w-3 h-3"></i> Embed
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'form_submissions' form.slug %}" class="inline-flex items-center gap-1 border border-blue-300 text-blue-700 hover:bg-blue-50 px-3 py-2 rounded-lg text-xs font-semibold transition">
|
||||||
|
<i data-lucide="list" class="w-3 h-3"></i> Submissions
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
{% if page_obj.has_other_pages %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4 mt-6">
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
Showing page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Forms pagination" class="flex items-center gap-2">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a href="?page=1{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="First">
|
||||||
|
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ page_obj.previous_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Previous">
|
||||||
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||||
|
{{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a href="?page={{ page_obj.next_page_number }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Next">
|
||||||
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ page_obj.paginator.num_pages }}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}{% if request.GET.sort %}&sort={{ request.GET.sort }}{% endif %}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Last">
|
||||||
|
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||||
|
<i data-lucide="layout-template" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">No forms found</h3>
|
||||||
|
<p class="text-gray-500 mb-6">Create your first form to get started.</p>
|
||||||
|
<a href="{% url 'form_builder' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="plus" class="w-5 h-5"></i> Create Form
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<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>
|
||||||
</div>
|
Form Preview
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<a href="{% url 'form_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> Back to Forms
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'form_embed' form.id %}" class="inline-flex items-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-4 py-2.5 rounded-xl text-sm transition font-medium" target="_blank">
|
||||||
|
<i data-lucide="code-2" class="w-4 h-4"></i> Get Embed Code
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
<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-emerald-600">{{ form.created_at|timesince }}</h4>
|
||||||
<h4 class="text-success">{{ form.created_at|timesince }}</h4>
|
<span class="text-sm text-gray-600 mt-1 block">Time Created</span>
|
||||||
<small class="text-muted">Time Created</small>
|
|
||||||
</div>
|
|
||||||
</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-info">{{ form.structure.wizards|length|default:0 }}</h4>
|
<span class="text-sm text-gray-600 mt-1 block">Form Steps</span>
|
||||||
<small class="text-muted">Form Steps</small>
|
|
||||||
</div>
|
|
||||||
</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-amber-600">
|
||||||
<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>
|
<span class="text-sm text-gray-600 mt-1 block">First Step Fields</span>
|
||||||
<small class="text-muted">First Step Fields</small>
|
|
||||||
</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="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">
|
<h2 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
<h2 class="text-primary fw-bold">{% trans "Submission Details" %}</h2>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary">
|
<i data-lucide="file-check-2" class="w-8 h-8 text-temple-red"></i>
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
|
</div>
|
||||||
</a>
|
{% trans "Submission Details" %}
|
||||||
</div>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Submissions" %}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
<tr>
|
<!-- Field Required Row -->
|
||||||
<td><strong>{% trans "Field Required" %}</strong></td>
|
<tr>
|
||||||
|
<td class="px-6 py-4 font-semibold text-gray-900 sticky left-0 bg-white z-10 border-r border-gray-200 whitespace-nowrap">{% trans "Field Required" %}</td>
|
||||||
{% for response in flat_responses %}
|
{% 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,373 +2,177 @@
|
|||||||
{% 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">
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary text-decoration-none">{% trans "Dashboard" %}</a></li>
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary text-decoration-none">{% trans "Form Templates" %}</a></li>
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'form_template_submissions_list' template.slug %}" class="text-secondary text-decoration-none">{% trans "Submissions" %}</a></li>
|
|
||||||
<li class="breadcrumb-item active" style="
|
|
||||||
color: #F43B5E;
|
|
||||||
font-weight: 600;
|
|
||||||
">{% trans "All Submissions Table" %}</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
<!-- Desktop Header -->
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="hidden lg:block">
|
||||||
<div>
|
<!-- Breadcrumb -->
|
||||||
<h1 class="h3 mb-1 d-flex align-items-center">
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
<i class="fas fa-table me-2"></i>
|
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||||
{% trans "All Submissions for" %}: <span class="text-white ms-2">{{ template.name }}</span>
|
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
|
||||||
|
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Dashboard" %}
|
||||||
|
</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li><a href="{% url 'form_templates_list' %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Form Templates" %}</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li><a href="{% url 'form_template_submissions_list' template.slug %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Submissions" %}</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li class="text-temple-red font-semibold">{% trans "All Submissions Table" %}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="table" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "All Submissions for" %}: {{ template.name }}
|
||||||
</h1>
|
</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>
|
||||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-light btn-sm">
|
<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 class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Submissions" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
</div>
|
||||||
{% if page_obj.object_list %}
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">{% trans "Submission ID" %}</th>
|
|
||||||
<th scope="col">{% trans "Applicant Name" %}</th>
|
|
||||||
<th scope="col">{% trans "Applicant Email" %}</th>
|
|
||||||
<th scope="col">{% trans "Submitted At" %}</th>
|
|
||||||
{% for field in fields %}
|
|
||||||
<th scope="col">{{ field.label }}</th>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for submission in page_obj %}
|
|
||||||
<tr>
|
|
||||||
<td class="fw-medium">{{ submission.id }}</td>
|
|
||||||
<td>{{ submission.applicant_name|default:"N/A" }}</td>
|
|
||||||
<td>{{ submission.applicant_email|default:"N/A" }}</td>
|
|
||||||
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
|
||||||
{% for field in fields %}
|
|
||||||
{% get_field_response_for_submission submission field as response %}
|
|
||||||
<td class="response-value">
|
|
||||||
{% if response %}
|
|
||||||
{% if response.uploaded_file %}
|
|
||||||
<div class="file-response">
|
|
||||||
|
|
||||||
<a href="{{ response.uploaded_file.url }}" class="btn btn-sm btn-outline-primary" target="_blank" title="Download File">
|
<!-- Mobile Header -->
|
||||||
<i class="fas fa-download"></i>
|
<div class="lg:hidden mb-4">
|
||||||
</a>
|
<div class="flex items-center justify-between mb-4">
|
||||||
</div>
|
<h1 class="text-xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
{% elif response.value %}
|
<div class="bg-temple-red/10 p-2 rounded-lg">
|
||||||
{% if response.field.field_type == 'checkbox' and response.value|length > 0 %}
|
<i data-lucide="table" class="w-5 h-5 text-temple-red"></i>
|
||||||
<div>
|
|
||||||
{% for val in response.value|to_list %}
|
|
||||||
<span class="badge bg-secondary badge-response">{{ val }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% elif response.field.field_type == 'radio' or response.field.field_type == 'select' %}
|
|
||||||
<span class="badge bg-info">{{ response.value }}</span>
|
|
||||||
{% else %}
|
|
||||||
<p class="mb-0">{{ response.value|linebreaksbr|truncatewords:10 }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">Not provided</span>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<span class="text-muted">Not provided</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% trans "All Submissions" %}
|
||||||
<!-- Pagination -->
|
</h1>
|
||||||
{% if page_obj.has_other_pages %}
|
<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">
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center mt-4">
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back" %}
|
||||||
<div class="pagination-info mb-3 mb-md-0">
|
</a>
|
||||||
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
|
||||||
Showing {{ start }} to {{ end }} of {{ total }} results.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</div>
|
|
||||||
<nav aria-label="Page navigation">
|
|
||||||
<ul class="pagination pagination-sm mb-0">
|
|
||||||
{% if page_obj.has_previous %}
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page=1" aria-label="First">
|
|
||||||
<span aria-hidden="true">«</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
|
|
||||||
<span aria-hidden="true">‹</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<li class="page-item active">
|
|
||||||
<span class="page-link">
|
|
||||||
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
|
||||||
</span>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% if page_obj.has_next %}
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
|
|
||||||
<span aria-hidden="true">›</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
|
|
||||||
<span aria-hidden="true">»</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<div class="empty-state">
|
|
||||||
<i class="fas fa-inbox"></i>
|
|
||||||
<h3 class="h5 mb-3">{% trans "No Submissions Found" %}</h3>
|
|
||||||
<p class="text-muted mb-4">
|
|
||||||
{% trans "There are no submissions for this form template yet." %}
|
|
||||||
</p>
|
|
||||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Submissions" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if page_obj.object_list %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
|
<i data-lucide="list" class="w-5 h-5"></i>
|
||||||
|
{% trans "All Submissions Table" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto max-h-[70vh]">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="sticky top-0 z-10">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Submission ID" %}</th>
|
||||||
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Applicant Name" %}</th>
|
||||||
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Applicant Email" %}</th>
|
||||||
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{% trans "Submitted At" %}</th>
|
||||||
|
{% for field in fields %}
|
||||||
|
<th scope="col" class="px-4 py-2 text-left text-[10px] font-bold text-white bg-temple-dark tracking-wider whitespace-nowrap uppercase">{{ field.label }}</th>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-100 text-sm">
|
||||||
|
{% for submission in page_obj %}
|
||||||
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
|
<td class="px-4 py-2 font-semibold text-gray-900 whitespace-nowrap">{{ submission.id }}</td>
|
||||||
|
<td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.applicant_name|default:"N/A" }}</td>
|
||||||
|
<td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.applicant_email|default:"N/A" }}</td>
|
||||||
|
<td class="px-4 py-2 text-gray-700 whitespace-nowrap">{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
||||||
|
{% for field in fields %}
|
||||||
|
{% get_field_response_for_submission submission field as response %}
|
||||||
|
<td class="px-4 py-2 max-w-[200px]">
|
||||||
|
{% if response %}
|
||||||
|
{% if response.uploaded_file %}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<a href="{{ response.uploaded_file.url }}"
|
||||||
|
class="inline-flex items-center gap-1 bg-temple-red/10 hover:bg-temple-red/20 text-temple-red px-2 py-1 rounded text-xs transition"
|
||||||
|
target="_blank"
|
||||||
|
title="{% trans 'Download File' %}">
|
||||||
|
<i data-lucide="download" class="w-3 h-3"></i> {% trans "Download" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% elif response.value %}
|
||||||
|
{% if response.field.field_type == 'checkbox' and response.value|length > 0 %}
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
{% for val in response.value|to_list %}
|
||||||
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-gray-200 text-gray-700">{{ val }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% elif response.field.field_type == 'radio' or response.field.field_type == 'select' %}
|
||||||
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-[10px] font-bold uppercase bg-blue-100 text-blue-800">{{ response.value }}</span>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-xs text-gray-700 line-clamp-2">{{ response.value|linebreaksbr|truncatewords:10 }}</p>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-xs text-gray-400">Not provided</span>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-xs text-gray-400">Not provided</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
{% if page_obj.has_other_pages %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||||
|
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
||||||
|
Showing {{ start }} to {{ end }} of {{ total }} results.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
<nav aria-label="Page navigation" class="flex items-center gap-2">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a href="?page=1" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="First">
|
||||||
|
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ page_obj.previous_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Previous">
|
||||||
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||||
|
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<a href="?page={{ page_obj.next_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Next">
|
||||||
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ page_obj.paginator.num_pages }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Last">
|
||||||
|
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<!-- No Results -->
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||||
|
<i data-lucide="inbox" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No Submissions Found" %}</h3>
|
||||||
|
<p class="text-gray-500 mb-6">{% trans "There are no submissions for this form template yet." %}</p>
|
||||||
|
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Submissions" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -1,375 +1,182 @@
|
|||||||
{% 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>
|
||||||
|
<li><a href="{% url 'form_templates_list' %}" class="text-gray-500 hover:text-temple-red transition">{% trans "Form Templates" %}</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li class="text-temple-red font-semibold">{% trans "Submissions" %}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<div class="flex-1">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="file-text" class="w-8 h-8 text-temple-red"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-2">
|
{% trans "Submissions for" %}: {{ template.name }}
|
||||||
<label for="date_from" class="form-label small text-muted">{% trans "From Date" %}</label>
|
</h1>
|
||||||
<input type="date" class="form-control" id="date_from" name="date_from" value="{{ request.GET.date_from }}">
|
<p class="text-sm text-gray-500 mt-2">Template ID: #{{ template.id }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<a href="{% url 'form_template_all_submissions' template.id %}" class="inline-flex items-center gap-2 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white px-4 py-2.5 rounded-xl text-sm transition font-medium">
|
||||||
|
<i data-lucide="table" class="w-4 h-4"></i> {% trans "View All in Table" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back to Templates" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Header -->
|
||||||
|
<div class="lg:hidden mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h1 class="text-xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<div class="bg-temple-red/10 p-2 rounded-lg">
|
||||||
|
<i data-lucide="file-text" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Submissions" %}
|
||||||
|
</h1>
|
||||||
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i> {% trans "Back" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if page_obj.object_list %}
|
||||||
|
<div id="form-template-submissions-list">
|
||||||
|
{# View Switcher #}
|
||||||
|
{% include "includes/_list_view_switcher.html" with list_id="form-template-submissions-list" %}
|
||||||
|
|
||||||
|
{# Table View (Default) #}
|
||||||
|
<div class="table-view active hidden lg:block bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
|
<i data-lucide="list" class="w-5 h-5"></i>
|
||||||
|
{% trans "Submission Listings" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Submission ID" %}</th>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Applicant Name" %}</th>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Applicant Email" %}</th>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Submitted At" %}</th>
|
||||||
|
<th scope="col" class="px-6 py-3 text-right text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-100">
|
||||||
|
{% for submission in page_obj %}
|
||||||
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
|
<td class="px-6 py-4 font-semibold text-gray-900">{{ submission.id }}</td>
|
||||||
|
<td class="px-6 py-4 text-sm text-gray-700">{{ submission.applicant_name|default:"N/A" }}</td>
|
||||||
|
<td class="px-6 py-4 text-sm text-gray-700">{{ submission.applicant_email|default:"N/A" }}</td>
|
||||||
|
<td class="px-6 py-4 text-sm text-gray-700">{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
||||||
|
<td class="px-6 py-4 text-right">
|
||||||
|
<a href="{% url 'form_submission_details' template_id=submission.template.id slug=submission.slug %}"
|
||||||
|
class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2 rounded-lg text-sm transition">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Details" %}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Card View #}
|
||||||
|
<div class="card-view lg:hidden grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{% for submission in page_obj %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<h3 class="h5 font-bold mb-1">{% trans "Submission" %} #{{ submission.id }}</h3>
|
||||||
|
<small class="text-white/70">{{ template.name }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-2">
|
<div class="p-4 space-y-2">
|
||||||
<label for="date_to" class="form-label small text-muted">{% trans "To Date" %}</label>
|
<p class="text-sm text-gray-700">
|
||||||
<input type="date" class="form-control" id="date_to" name="date_to" value="{{ request.GET.date_to }}">
|
<span class="font-semibold">{% trans "Applicant Name" %}:</span> {{ submission.applicant_name|default:"N/A" }}<br>
|
||||||
|
<span class="font-semibold">{% trans "Applicant Email" %}:</span> {{ submission.applicant_email|default:"N/A" }}<br>
|
||||||
|
<span class="font-semibold">{% trans "Submitted At" %}:</span> {{ submission.submitted_at|date:"M d, Y H:i" }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3 d-flex gap-2">
|
<div class="p-4 pt-0">
|
||||||
<button type="submit" class="btn btn-main-action flex-grow-1">
|
<a href="{% url 'form_submission_details' template_id=template.id slug=submission.slug %}"
|
||||||
<i class="fas fa-search me-1"></i> {% trans "Filter" %}
|
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">
|
||||||
</button>
|
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Details" %}
|
||||||
<a href="?" class="btn btn-outline-secondary flex-grow-1">
|
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
{% endfor %}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endcomment %}
|
|
||||||
<div class="container py-4">
|
|
||||||
<nav aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">{% trans "Dashboard" %}</a></li>
|
|
||||||
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary">{% trans "Form Templates" %}</a></li>
|
|
||||||
<li class="breadcrumb-item active" style="
|
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;
|
|
||||||
">{% trans "Submissions" %}</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div class="card shadow-sm">
|
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
|
||||||
<div>
|
|
||||||
<h1 class="h3 mb-1 d-flex align-items-center">
|
|
||||||
<i class="fas fa-file-alt me-2"></i>
|
|
||||||
{% trans "Submissions for" %}: <span class="text-white ms-2">{{ template.name }}</span>
|
|
||||||
</h1>
|
|
||||||
<small class="text-white-50">Template ID: #{{ template.id }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<a href="{% url 'form_template_all_submissions' template.id %}" class="btn btn-outline-light btn-sm">
|
|
||||||
<i class="fas fa-table me-1"></i> {% trans "View All in Table" %}
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'form_templates_list' %}" class="btn btn-outline-light btn-sm">
|
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Templates" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
|
||||||
{% if page_obj.object_list %}
|
|
||||||
<div id="form-template-submissions-list">
|
|
||||||
{# View Switcher #}
|
|
||||||
{% include "includes/_list_view_switcher.html" with list_id="form-template-submissions-list" %}
|
|
||||||
|
|
||||||
{# Table View (Default) #}
|
<!-- Pagination -->
|
||||||
<div class="table-view active">
|
{% if page_obj.has_other_pages %}
|
||||||
<div class="table-responsive mb-4">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||||
<table class="table table-hover">
|
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
||||||
<thead>
|
<div class="text-sm text-gray-600">
|
||||||
<tr>
|
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
||||||
<th scope="col">{% trans "Submission ID" %}</th>
|
Showing {{ start }} to {{ end }} of {{ total }} results.
|
||||||
<th scope="col">{% trans "Applicant Name" %}</th>
|
{% endblocktrans %}
|
||||||
<th scope="col">{% trans "Applicant Email" %}</th>
|
|
||||||
<th scope="col">{% trans "Submitted At" %}</th>
|
|
||||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for submission in page_obj %}
|
|
||||||
<tr>
|
|
||||||
<td class="fw-medium">{{ submission.id }}</td>
|
|
||||||
<td>{{ submission.applicant_name|default:"N/A" }}</td>
|
|
||||||
<td>{{ submission.applicant_email|default:"N/A" }}</td>
|
|
||||||
<td>{{ submission.submitted_at|date:"M d, Y H:i" }}</td>
|
|
||||||
<td class="text-end">
|
|
||||||
<a href="{% url 'form_submission_details' template_id=submission.template.id slug=submission.slug %}" class="btn btn-sm btn-outline-primary">
|
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Card View #}
|
|
||||||
<div class="card-view">
|
|
||||||
<div class="row g-4">
|
|
||||||
{% for submission in page_obj %}
|
|
||||||
<div class="col-md-6 col-lg-6">
|
|
||||||
<div class="card h-100">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3 class="h5 mb-2">{% trans "Submission" %} #{{ submission.id }}</h3>
|
|
||||||
<small class="text-white-50">{{ template.name }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="card-text">
|
|
||||||
<strong>{% trans "Applicant Name" %}:</strong> {{ submission.applicant_name|default:"N/A" }}<br>
|
|
||||||
<strong>{% trans "Applicant Email" %}:</strong> {{ submission.applicant_email|default:"N/A" }}<br>
|
|
||||||
<strong>{% trans "Submitted At" %}:</strong> {{ submission.submitted_at|date:"M d, Y H:i" }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
|
||||||
<a href="{% url 'form_submission_details' template_id=template.id slug=submission.slug %}" class="btn btn-sm btn-outline-primary w-100">
|
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<nav aria-label="Page navigation" class="flex items-center gap-2">
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
<a href="?page=1" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="First">
|
||||||
|
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
<a href="?page={{ page_obj.previous_page_number }}" class="px-3 py-2 bg-white border border-gray-200 rounded-lg text-sm text-gray-700 hover:bg-gray-50 transition" aria-label="Previous">
|
||||||
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Pagination -->
|
<span class="px-4 py-2 bg-temple-red text-white rounded-lg text-sm font-semibold">
|
||||||
{% if page_obj.has_other_pages %}
|
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
||||||
<div class="d-flex flex-column flex-md-row justify-content-between align-items-center">
|
</span>
|
||||||
<div class="pagination-info mb-3 mb-md-0">
|
|
||||||
{% blocktrans with start=page_obj.start_index end=page_obj.end_index total=page_obj.paginator.count %}
|
|
||||||
Showing {{ start }} to {{ end }} of {{ total }} results.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</div>
|
|
||||||
<nav aria-label="Page navigation">
|
|
||||||
<ul class="pagination pagination-sm mb-0">
|
|
||||||
{% if page_obj.has_previous %}
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page=1" aria-label="First">
|
|
||||||
<span aria-hidden="true">«</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}" aria-label="Previous">
|
|
||||||
<span aria-hidden="true">‹</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<li class="page-item active">
|
{% if page_obj.has_next %}
|
||||||
<span class="page-link">
|
<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">
|
||||||
{% trans "Page" %} {{ page_obj.number }} {% trans "of" %} {{ page_obj.paginator.num_pages }}
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
||||||
</span>
|
</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">
|
||||||
|
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||||
{% if page_obj.has_next %}
|
</a>
|
||||||
<li class="page-item">
|
{% endif %}
|
||||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}" aria-label="Next">
|
</nav>
|
||||||
<span aria-hidden="true">›</span>
|
</div>
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="page-item">
|
|
||||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}" aria-label="Last">
|
|
||||||
<span aria-hidden="true">»</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<div class="empty-state">
|
|
||||||
<i class="fas fa-inbox"></i>
|
|
||||||
<h3 class="h5 mb-3">{% trans "No Submissions Found" %}</h3>
|
|
||||||
<p class="text-muted mb-4">
|
|
||||||
{% trans "There are no submissions for this form template yet." %}
|
|
||||||
</p>
|
|
||||||
<a href="{% url 'form_templates_list' %}" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Templates" %}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<!-- No Results -->
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-12 text-center">
|
||||||
|
<i data-lucide="inbox" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No Submissions Found" %}</h3>
|
||||||
|
<p class="text-gray-500 mb-6">{% trans "There are no submissions for this form template yet." %}</p>
|
||||||
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-3 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="arrow-left" class="w-5 h-5"></i> {% trans "Back to Templates" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
lucide.createIcons();
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -4,189 +4,107 @@
|
|||||||
|
|
||||||
{% 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">
|
||||||
</h1>
|
<!-- Breadcrumb -->
|
||||||
<button type="button" class="btn btn-main-action" data-bs-toggle="modal" data-bs-target="#createTemplateModal">
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
<i class="fas fa-plus me-1"></i> {% trans "Create New Template" %}
|
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||||
</button>
|
<li><a href="{% url 'dashboard' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
|
||||||
|
<i data-lucide="home" class="w-4 h-4"></i> {% trans "Home" %}
|
||||||
|
</a></li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li class="text-temple-red font-semibold">{% trans "Form Templates" %}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
|
<i data-lucide="file-text" class="w-8 h-8 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Form Templates" %}
|
||||||
|
</h1>
|
||||||
|
<button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md"
|
||||||
|
onclick="document.getElementById('createTemplateModal').classList.remove('hidden'); document.getElementById('createTemplateModal').classList.add('flex');">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create New Template" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop Filters -->
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden mb-6">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
|
<i data-lucide="filter" class="w-5 h-5"></i>
|
||||||
|
{% trans "Filter Templates" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<label for="searchInput" class="block text-xs font-semibold text-gray-600 mb-2">{% trans "Search by Template Name" %}</label>
|
||||||
|
<form method="get" class="flex gap-3">
|
||||||
|
<div class="relative flex-1">
|
||||||
|
<i data-lucide="search" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i>
|
||||||
|
<input type="text" name="q" id="searchInput"
|
||||||
|
class="w-full pl-10 pr-4 py-3 bg-white border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
|
||||||
|
placeholder="{% trans 'Search templates by name...' %}"
|
||||||
|
value="{{ query|default_if_none:'' }}">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-5 py-2.5 rounded-xl text-sm transition whitespace-nowrap">
|
||||||
|
<i data-lucide="filter" class="w-4 h-4"></i> {% trans "Search" %}
|
||||||
|
</button>
|
||||||
|
{% if query %}
|
||||||
|
<a href="{% url 'form_templates_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition whitespace-nowrap">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i> {% trans "Clear" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Search/Filter Area - Matching Standard Structure #}
|
<!-- Mobile Header -->
|
||||||
<div class="card mb-4 shadow-sm no-hover">
|
<div class="lg:hidden mb-4">
|
||||||
<div class="card-body">
|
<div class="flex items-center justify-between mb-4">
|
||||||
<form method="get" class="row g-3 align-items-end">
|
<h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
|
<div class="bg-temple-red/10 p-2 rounded-lg">
|
||||||
|
<i data-lucide="file-text" class="w-6 h-6 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Form Templates" %}
|
||||||
|
</h1>
|
||||||
|
<button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl text-sm transition shadow-sm"
|
||||||
|
onclick="document.getElementById('createTemplateModal').classList.remove('hidden'); document.getElementById('createTemplateModal').classList.add('flex');">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<!-- Mobile Filters -->
|
||||||
<label for="searchInput" class="form-label small text-muted">{% trans "Search by Template Name" %}</label>
|
<div class="space-y-3">
|
||||||
<div class="input-group input-group-lg">
|
<div>
|
||||||
<span class="input-group-text"><i class="fas fa-search text-muted"></i></span>
|
<label for="searchInputMobile" class="block text-xs font-semibold text-gray-600 mb-2">{% trans "Search Templates" %}</label>
|
||||||
<input type="text" name="q" id="searchInput" class="form-control form-control-search"
|
<form method="get" class="flex gap-2">
|
||||||
placeholder="{% trans 'Search templates by name...' %}"
|
<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:'' }}">
|
value="{{ query|default_if_none:'' }}">
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
<div class="col-md-6">
|
</button>
|
||||||
<div class="filter-buttons">
|
{% if query %}
|
||||||
<button type="submit" class="btn btn-main-action btn-lg">
|
<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 class="fas fa-filter me-1"></i> {% trans "Search" %}
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
</button>
|
|
||||||
|
|
||||||
{# Show Clear button if search is active #}
|
|
||||||
{% if query %}
|
|
||||||
<a href="{% url 'form_templates_list' %}" class="btn btn-outline-secondary btn-lg">
|
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Clear Search" %}
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -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>
|
</div>
|
||||||
|
<div class="p-4 space-y-3">
|
||||||
{# Stats #}
|
{# Stats #}
|
||||||
<div class="row text-center mb-3">
|
<div class="grid grid-cols-2 gap-2 text-center mb-3">
|
||||||
<div class="col-6 border-end">
|
<div class="p-3 bg-gray-50 rounded-xl">
|
||||||
<div class="stat-value">{{ template.get_stage_count }}</div>
|
<div class="text-2xl font-bold text-temple-red">{{ template.get_stage_count }}</div>
|
||||||
<div class="stat-label">{% trans "Stages" %}</div>
|
<div class="text-[10px] uppercase text-gray-500 font-semibold">{% trans "Stages" %}</div>
|
||||||
</div>
|
|
||||||
<div class="col-6">
|
|
||||||
<div class="stat-value">{{ template.get_field_count }}</div>
|
|
||||||
<div class="stat-label">{% trans "Fields" %}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-3 bg-gray-50 rounded-xl">
|
||||||
{# Description #}
|
<div class="text-2xl font-bold text-temple-red">{{ template.get_field_count }}</div>
|
||||||
<p class="card-text small text-muted flex-grow-1">
|
<div class="text-[10px] uppercase text-gray-500 font-semibold">{% trans "Fields" %}</div>
|
||||||
{% if template.description %}
|
|
||||||
{{ template.description|truncatewords:20 }}
|
|
||||||
{% else %}
|
|
||||||
<em class="text-muted">{% trans "No description provided" %}</em>
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{# Action area #}
|
|
||||||
<div class="mt-auto pt-2 border-top">
|
|
||||||
<div class="d-flex gap-2 justify-content-end">
|
|
||||||
|
|
||||||
<a href="{% url 'application_submit_form' template.job.slug %}" class="btn btn-outline-primary btn-sm" title="{% trans 'Preview' %}">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'form_builder' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Edit' %}">
|
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'form_template_submissions_list' template.slug %}" class="btn btn-outline-secondary btn-sm" title="{% trans 'Submissions' %}">
|
|
||||||
<i class="fas fa-file-alt"></i>
|
|
||||||
</a>
|
|
||||||
<button type="button" class="btn btn-outline-danger btn-sm" title="{% trans 'Delete' %}"
|
|
||||||
data-bs-toggle="modal" data-bs-target="#deleteModal"
|
|
||||||
data-delete-url="#"
|
|
||||||
data-item-name="{{ template.name }}">
|
|
||||||
<i class="fas fa-trash-alt"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer bg-light text-muted small">
|
|
||||||
<div class="d-flex justify-content-between">
|
{# Description #}
|
||||||
<span><i class="fas fa-calendar-alt me-1"></i> {% trans "Created:" %} {{ template.created_at|date:"M d, Y" }}</span>
|
<p class="text-sm text-gray-600">
|
||||||
<span><i class="fas fa-sync-alt me-1"></i> {{ template.updated_at|timesince }} {% trans "ago" %}</span>
|
{% if template.description %}
|
||||||
</div>
|
{{ template.description|truncatewords:20 }}
|
||||||
|
{% else %}
|
||||||
|
<em class="text-gray-400">{% trans "No description provided" %}</em>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{# Actions #}
|
||||||
|
<div class="flex gap-2 pt-3 border-t border-gray-100">
|
||||||
|
<a href="{% url 'application_submit_form' template.job.slug %}" class="inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition flex-1">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "Preview" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'form_builder' template.slug %}" class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-gray-300 hover:bg-gray-100 text-gray-600 transition">
|
||||||
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'form_template_submissions_list' template.slug %}" class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-gray-300 hover:bg-gray-100 text-gray-600 transition">
|
||||||
|
<i data-lucide="file-text" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
<button type="button" class="inline-flex items-center justify-center w-10 h-10 rounded-xl border border-red-300 bg-red-50 hover:bg-red-100 text-red-600 transition"
|
||||||
|
onclick="deleteTemplate('{{ template.name }}', '#')">
|
||||||
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bg-gray-50 text-gray-500 text-xs p-3 flex justify-between">
|
||||||
|
<span><i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i> {% trans "Created:" %} {{ template.created_at|date:"M d, Y" }}</span>
|
||||||
|
<span><i data-lucide="refresh-cw" class="w-3 h-3 inline mr-1"></i> {{ template.updated_at|timesince }} {% trans "ago" %}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</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 %}
|
<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">
|
||||||
<li class="page-item">
|
{% trans "First" %}
|
||||||
<a class="page-link" href="?page=1{% if query %}&q={{ query }}{% endif %}">First</a>
|
</a>
|
||||||
</li>
|
<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">
|
||||||
<li class="page-item">
|
{% trans "Previous" %}
|
||||||
<a class="page-link" href="?page={{ templates.previous_page_number }}{% if query %}&q={{ query }}{% endif %}">Previous</a>
|
</a>
|
||||||
</li>
|
{% 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');">
|
||||||
</button>
|
<i data-lucide="plus" class="w-5 h-5"></i> {% trans "Create Your First Template" %}
|
||||||
</div>
|
</button>
|
||||||
</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>
|
<label class="flex items-center gap-2 cursor-pointer">
|
||||||
|
<input type="radio" name="exam_status" id="exam_failed" value="Failed" {% if application.exam_status == 'Failed' %}checked{% endif %} class="w-4 h-4 text-temple-red border-gray-300 focus:ring-temple-red/20">
|
||||||
|
<span class="text-gray-700 text-sm"><i data-lucide="x" class="w-4 h-4 inline text-red-600"></i> {% trans "Failed" %}</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-center items-center mt-4 gap-4">
|
||||||
|
<div class="w-24 text-right">
|
||||||
|
<label for="exam_score" class="block text-sm font-medium text-gray-600">{% trans "Exam Score" %}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check d-flex align-items-center gap-2">
|
<div class="w-24">
|
||||||
<input class="form-check-input" type="radio" name="exam_status" id="exam_failed" value="Failed" {% if application.exam_status == 'Failed' %}checked{% endif %}>
|
<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 }}">
|
||||||
<label class="form-check-label" for="exam_failed">
|
</div>
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Failed" %}
|
<div class="w-24 text-left">
|
||||||
</label>
|
|
||||||
</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 class="contact-item">
|
|
||||||
<span>✉️</span>
|
|
||||||
<span>Bakhsh.Abdullah@Outlook.com</span>
|
|
||||||
</div>
|
|
||||||
<div class="contact-item">
|
|
||||||
<span>📍</span>
|
|
||||||
<span>Saudi Arabia</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
{% if application.phone %}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
<div class="main-content">
|
<span class="text-lg">✉️</span>
|
||||||
<div class="left-column">
|
<span>{{ application.phone }}</span>
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">
|
|
||||||
<span>📋</span> Professional Summary
|
|
||||||
</h2>
|
|
||||||
<p class="summary">
|
|
||||||
Strategic and results‑driven HR leader with over 11 years of experience in human resources, specializing in business partnering, workforce planning, talent acquisition, training & development, and organizational effectiveness.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="section">
|
{% if application.location %}
|
||||||
<h2 class="section-title">
|
<div class="flex items-center gap-2">
|
||||||
<span>💼</span> Work Experience
|
<span class="text-lg">📍</span>
|
||||||
</h2>
|
<span>{{ application.location }}</span>
|
||||||
<div class="experience-item">
|
|
||||||
<div class="job-header">
|
|
||||||
<div>
|
|
||||||
<div class="job-title">Head, Recruitment & Training</div>
|
|
||||||
<div class="company">TASNEE - DOWNSTREAM & METALLURGY SBUs</div>
|
|
||||||
</div>
|
|
||||||
<div class="job-period">Oct 2024 - Present</div>
|
|
||||||
</div>
|
|
||||||
<ul class="achievements">
|
|
||||||
<li>Led recruitment and training initiatives across downstream and metallurgical divisions</li>
|
|
||||||
<li>Developed workforce analytics program</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="job-header">
|
|
||||||
<div>
|
|
||||||
<div class="job-title">Human Resources Business Partner</div>
|
|
||||||
<div class="company">TASNEE – METALLURGY SBU</div>
|
|
||||||
</div>
|
|
||||||
<div class="job-period">Oct 2015 - Present</div>
|
|
||||||
</div>
|
|
||||||
<ul class="achievements">
|
|
||||||
<li>Implemented HR strategies aligning with business goals</li>
|
|
||||||
<li>Optimized recruitment processes</li>
|
|
||||||
<li>Ensured regulatory compliance</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="experience-item">
|
|
||||||
<div class="job-header">
|
|
||||||
<div>
|
|
||||||
<div class="job-title">Specialist, Recruitment</div>
|
|
||||||
<div class="company">MARAFIQ</div>
|
|
||||||
</div>
|
|
||||||
<div class="job-period">Jul 2011 - Feb 2013</div>
|
|
||||||
</div>
|
|
||||||
<ul class="achievements">
|
|
||||||
<li>Performed recruitment for various roles</li>
|
|
||||||
<li>Improved selection processes</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">
|
|
||||||
<span>🎓</span> Education
|
|
||||||
</h2>
|
|
||||||
<div class="education-item">
|
|
||||||
<div class="degree">Associate Diploma in People Management Level 5</div>
|
|
||||||
<div class="institution">Chartered Institute of Personnel and Development (CIPD)</div>
|
|
||||||
</div>
|
|
||||||
<div class="education-item">
|
|
||||||
<div class="degree">Bachelor's Degree in Chemical Engineering Technology</div>
|
|
||||||
<div class="institution">Yanbu Industrial College</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="right-column">
|
|
||||||
<div class="score-card">
|
|
||||||
<div class="match-score">10%</div>
|
|
||||||
<div class="score-label">Match Score</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">
|
|
||||||
<span>🔍</span> Assessment
|
|
||||||
</h2>
|
|
||||||
<div class="strengths-weaknesses">
|
|
||||||
<div class="strength-box">
|
|
||||||
<div class="box-title">Strengths</div>
|
|
||||||
<div class="box-content">Extensive HR leadership and project management experience</div>
|
|
||||||
</div>
|
|
||||||
<div class="weakness-box">
|
|
||||||
<div class="box-title">Weaknesses</div>
|
|
||||||
<div class="box-content">Lack of IT infrastructure, cybersecurity, and relevant certifications</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="recommendation">
|
|
||||||
<div class="recommendation-title">Recommendation</div>
|
|
||||||
<div class="recommendation-text">Candidate does not meet the IT management requirements; not recommended for interview.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">
|
|
||||||
<span>💡</span> Top Keywords
|
|
||||||
</h2>
|
|
||||||
<div class="skills-container">
|
|
||||||
<span class="keyword-tag">HR</span>
|
|
||||||
<span class="keyword-tag">Recruitment</span>
|
|
||||||
<span class="keyword-tag">Training</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">
|
|
||||||
<span>🛠️</span> Skills
|
|
||||||
</h2>
|
|
||||||
<div class="skills-container">
|
|
||||||
<span class="skill-tag">Workforce Analytics</span>
|
|
||||||
<span class="skill-tag">Succession Planning</span>
|
|
||||||
<span class="skill-tag">Organizational Development</span>
|
|
||||||
<span class="skill-tag">Recruitment & Selection</span>
|
|
||||||
<span class="skill-tag">Employee Engagement</span>
|
|
||||||
<span class="skill-tag">Training Needs Analysis</span>
|
|
||||||
<span class="skill-tag">Performance Management</span>
|
|
||||||
<span class="skill-tag">Time Management</span>
|
|
||||||
<span class="skill-tag">Negotiation Skills</span>
|
|
||||||
<span class="skill-tag">SAP & HRIS Systems</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="section">
|
|
||||||
<h2 class="section-title">
|
|
||||||
<span>🌍</span> Languages
|
|
||||||
</h2>
|
|
||||||
<div class="language-item">
|
|
||||||
<span class="language-name">Arabic</span>
|
|
||||||
<span class="proficiency">Native</span>
|
|
||||||
</div>
|
|
||||||
<div class="language-item">
|
|
||||||
<span class="language-name">English</span>
|
|
||||||
<span class="proficiency">Fluent</span>
|
|
||||||
</div>
|
|
||||||
<div class="language-item">
|
|
||||||
<span class="language-name">Japanese</span>
|
|
||||||
<span class="proficiency">Intermediate</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
|
||||||
</html>
|
<!-- Main Content -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-[1fr_350px] gap-8 p-10">
|
||||||
|
|
||||||
|
<!-- Left Column -->
|
||||||
|
<div class="flex flex-col gap-8">
|
||||||
|
<!-- Professional Summary -->
|
||||||
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>📋</span> {% trans "Professional Summary" %}
|
||||||
|
</h2>
|
||||||
|
<p class="text-gray-600 leading-8">
|
||||||
|
{% if application.ai_analysis_data %}
|
||||||
|
{{ application.ai_analysis_data.parsed_summary }}
|
||||||
|
{% elif application.parsed_summary %}
|
||||||
|
{{ application.parsed_summary }}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-gray-400 italic">{% trans "No summary available" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Work Experience -->
|
||||||
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>💼</span> {% trans "Work Experience" %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{% if application.ai_analysis_data.work_experience %}
|
||||||
|
{% for exp in application.ai_analysis_data.work_experience %}
|
||||||
|
<div class="mb-6 pb-5 border-b border-gray-200 last:border-0 last:mb-0 last:pb-0">
|
||||||
|
<div class="flex justify-between items-start mb-2.5">
|
||||||
|
<div>
|
||||||
|
<div class="font-bold text-gray-900 text-lg">{{ exp.title }}</div>
|
||||||
|
<div class="text-gray-600 font-medium">{{ exp.company }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-gray-500 text-sm whitespace-nowrap">{{ exp.period }}</div>
|
||||||
|
</div>
|
||||||
|
<ul class="list-disc list-inside pl-5 text-gray-600 space-y-1.5">
|
||||||
|
{% for responsibility in exp.responsibilities %}
|
||||||
|
<li>{{ responsibility }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p class="text-gray-400 italic">{% trans "No work experience data available" %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Education -->
|
||||||
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>🎓</span> {% trans "Education" %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{% if application.ai_analysis_data.education %}
|
||||||
|
{% for edu in application.ai_analysis_data.education %}
|
||||||
|
<div class="mb-4 pb-4 border-b border-gray-200 last:border-0 last:mb-0 last:pb-0">
|
||||||
|
<div class="font-bold text-gray-900">{{ edu.degree }}</div>
|
||||||
|
<div class="text-gray-600">{{ edu.institution }}</div>
|
||||||
|
<div class="text-gray-500 text-sm mt-1">{{ edu.year }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<p class="text-gray-400 italic">{% trans "No education data available" %}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column -->
|
||||||
|
<div class="flex flex-col gap-6">
|
||||||
|
<!-- Match Score -->
|
||||||
|
{% if application.ai_analysis_data %}
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white text-center p-8 rounded-2xl shadow-lg">
|
||||||
|
<div class="text-5xl font-bold mb-2.5">{{ application.ai_analysis_data.match_score }}%</div>
|
||||||
|
<div class="text-lg opacity-90">{% trans "Match Score" %}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Assessment -->
|
||||||
|
{% if application.ai_analysis_data %}
|
||||||
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>🔍</span> {% trans "Assessment" %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-4">
|
||||||
|
<div class="bg-emerald-50 border border-emerald-500 p-4 rounded-xl">
|
||||||
|
<div class="font-bold text-emerald-700 mb-2 text-sm flex items-center gap-2">
|
||||||
|
<span>✅</span> {% trans "Strengths" %}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.strengths }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-red-50 border border-red-500 p-4 rounded-xl">
|
||||||
|
<div class="font-bold text-red-700 mb-2 text-sm flex items-center gap-2">
|
||||||
|
<span>❌</span> {% trans "Weaknesses" %}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.weaknesses }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-temple-red/5 border-l-4 border-l-temple-red p-4 rounded-xl">
|
||||||
|
<div class="font-bold text-temple-red mb-2 flex items-center gap-2">
|
||||||
|
<span>💡</span> {% trans "Recommendation" %}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-700">{{ application.ai_analysis_data.recommendation }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Top Keywords -->
|
||||||
|
{% if application.ai_analysis_data %}
|
||||||
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>💡</span> {% trans "Top Keywords" %}
|
||||||
|
</h2>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{% for keyword in application.ai_analysis_data.top_3_keywords %}
|
||||||
|
<span class="bg-temple-red/10 text-temple-red px-3 py-1.5 rounded-full text-sm font-medium border border-temple-red/20">
|
||||||
|
{{ keyword }}
|
||||||
|
</span>
|
||||||
|
{% empty %}
|
||||||
|
<span class="text-gray-400 italic text-sm">{% trans "No keywords available" %}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Skills -->
|
||||||
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>🛠️</span> {% trans "Skills" %}
|
||||||
|
</h2>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{% if application.ai_analysis_data.skills %}
|
||||||
|
{% for skill in application.ai_analysis_data.skills %}
|
||||||
|
<span class="bg-gradient-to-r from-temple-red to-[#7a1a29] text-white px-3 py-1.5 rounded-full text-sm font-medium shadow-sm">
|
||||||
|
{{ skill }}
|
||||||
|
</span>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<span class="text-gray-400 italic text-sm">{% trans "No skills data available" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Professional Details -->
|
||||||
|
{% if application.ai_analysis_data %}
|
||||||
|
<div class="bg-gray-50 rounded-2xl p-6 border border-gray-200 hover:shadow-xl transition-all duration-300 hover:-translate-y-0.5">
|
||||||
|
<h2 class="text-xl font-bold text-temple-red mb-5 pb-2.5 border-b-2 border-temple-red flex items-center gap-2.5">
|
||||||
|
<span>👤</span> {% trans "Professional Details" %}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center mb-3">
|
||||||
|
<span class="text-gray-600 text-sm">{% trans "Years of Experience:" %}</span>
|
||||||
|
<strong class="text-gray-900">{{ application.ai_analysis_data.years_of_experience }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center mb-3">
|
||||||
|
<span class="text-gray-600 text-sm">{% trans "Recent Job Title:" %}</span>
|
||||||
|
<strong class="text-gray-900 text-sm text-right">{{ application.ai_analysis_data.most_recent_job_title }}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center mb-3">
|
||||||
|
<span class="text-gray-600 text-sm">{% trans "Industry Match:" %}</span>
|
||||||
|
<span class="px-2.5 py-0.5 rounded-full text-xs font-bold {% if application.ai_analysis_data.experience_industry_match >= 70 %}bg-emerald-500 text-white{% elif application.ai_analysis_data.experience_industry_match >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
|
||||||
|
{{ application.ai_analysis_data.experience_industry_match }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-gray-600 text-sm">{% trans "Soft Skills Score:" %}</span>
|
||||||
|
<strong class="text-gray-900">{{ application.ai_analysis_data.soft_skills_score }}%</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|||||||
@ -1,23 +1,26 @@
|
|||||||
<div class="card mt-4">
|
{% 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 class="text-gray-900">{{ comment.author.get_full_name|default:comment.author.username }}</strong>
|
||||||
<strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong>
|
{% if comment.author != user %}
|
||||||
{% if comment.author != user %}
|
<span class="inline-block ml-2 px-2 py-0.5 bg-gray-200 text-gray-600 text-xs rounded-full">{% trans "Comment" %}</span>
|
||||||
<span class="badge bg-secondary ms-2">{% trans "Comment" %}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<small class="text-muted">{{ comment.created_at|date:"M d, Y P" }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<p class="card-text">{{ comment.content|safe }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer">
|
|
||||||
{% if comment.author == user or user.is_staff %}
|
|
||||||
<div class="btn-group btn-group-sm">
|
|
||||||
<button type="button" class="btn btn-outline-primary"
|
|
||||||
hx-get="{% url 'edit_meeting_comment' meeting.slug comment.id %}"
|
|
||||||
hx-target="#comment-section"
|
|
||||||
title="Edit Comment">
|
|
||||||
<i class="bi bi-pencil"></i>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-outline-danger"
|
|
||||||
hx-get="{% url 'delete_meeting_comment' meeting.slug comment.id %}"
|
|
||||||
hx-target="#comment-section"
|
|
||||||
title="Delete Comment">
|
|
||||||
<i class="bi bi-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<small class="text-gray-500 text-sm">{{ comment.created_at|date:"M d, Y P" }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-3">
|
||||||
|
<p class="text-gray-700 mb-0">{{ comment.content|safe }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-2 bg-gray-50 border-t border-gray-100">
|
||||||
|
{% if comment.author == user or user.is_staff %}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button type="button" class="inline-flex items-center gap-1 px-3 py-1.5 border border-temple-red text-temple-red hover:bg-temple-red hover:text-white rounded-lg text-sm transition"
|
||||||
|
hx-get="{% url 'edit_meeting_comment' meeting.slug comment.id %}"
|
||||||
|
hx-target="#comment-section"
|
||||||
|
title="Edit Comment">
|
||||||
|
<i data-lucide="pencil" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="inline-flex items-center gap-1 px-3 py-1.5 border border-red-500 text-red-500 hover:bg-red-500 hover:text-white rounded-lg text-sm transition"
|
||||||
|
hx-get="{% url 'delete_meeting_comment' meeting.slug comment.id %}"
|
||||||
|
hx-target="#comment-section"
|
||||||
|
title="Delete Comment">
|
||||||
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
<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>
|
||||||
<div class="toast-body">
|
<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')">
|
||||||
</div>
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
<div id="meetingModalBody" class="modal-body px-4 py-3">
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
<div id="meetingModalBody" class="p-4">
|
||||||
|
|
||||||
</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 -->
|
||||||
|
<nav aria-label="breadcrumb" class="mb-4">
|
||||||
|
<ol class="flex items-center gap-2 text-sm">
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'settings' %}" class="text-gray-600 hover:text-temple-red transition">{% trans "Settings" %}</a>
|
||||||
|
</li>
|
||||||
|
<li class="text-gray-400">/</li>
|
||||||
|
<li class="font-semibold text-temple-red">{% trans "System Activity" %}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<div class="container-fluid pt-5 pb-5 px-lg-5" style="background-color: var(--color-background-light);">
|
<!-- Header -->
|
||||||
<nav aria-label="breadcrumb">
|
<div class="mb-4">
|
||||||
<ol class="breadcrumb">
|
<h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
||||||
<li class="breadcrumb-item"><a href="{% url 'settings' %}" class="text-decoration-none text-secondary">{% trans "Settings" %}</a></li>
|
<i data-lucide="shield" class="w-6 h-6 text-temple-red"></i>
|
||||||
<li class="breadcrumb-item active" aria-current="page" style="
|
{% trans "System Activity Logs" %}
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;
|
|
||||||
">{% trans "System Activity" %}</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
<h1 class="h3 fw-bold dashboard-header mb-4 px-3">
|
|
||||||
<i class="fas fa-shield-alt me-2" style="color: var(--color-primary);"></i>{% trans "System Activity Logs" %}
|
|
||||||
</h1>
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="alert summary-alert border-start border-5 p-3 mb-5 mx-3" role="alert">
|
<!-- Summary Alert -->
|
||||||
<h6 class="mb-1">{% trans "Viewing Logs" %}: <strong>{{ tab_title }}</strong></h6>
|
<div class="bg-temple-red/10 border-l-4 border-temple-red p-4 mb-6 rounded-r-lg">
|
||||||
<p class="mb-0 small">
|
<div class="text-sm font-semibold text-gray-800 mb-1">
|
||||||
{% trans "Displaying" %}: **{{ logs.start_index }}-{{ logs.end_index }}** {% trans "of" %}
|
{% trans "Viewing Logs" %}: <strong>{{ tab_title }}</strong>
|
||||||
**{{ total_count }}** {% trans "total records." %}
|
</div>
|
||||||
</p>
|
<p class="text-sm text-gray-700">
|
||||||
|
{% trans "Displaying" %}: <strong>{{ logs.start_index }}-{{ logs.end_index }}</strong> {% trans "of" %}
|
||||||
|
<strong>{{ total_count }}</strong> {% trans "total records." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Audit Card -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 min-h-[60vh]">
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="flex border-b border-gray-200 px-3 pt-4 rounded-t-xl">
|
||||||
|
<a class="px-4 py-2 text-sm font-medium transition border-b-3 {% if active_tab == 'crud' %}text-temple-red border-temple-red{% else %}text-gray-600 border-transparent hover:text-temple-red hover:border-gray-300{% endif %}"
|
||||||
|
href="?tab=crud">
|
||||||
|
<i data-lucide="database" class="w-4 h-4 inline mr-2"></i>{% trans "Model Changes (CRUD)" %}
|
||||||
|
</a>
|
||||||
|
<a class="px-4 py-2 text-sm font-medium transition border-b-3 {% if active_tab == 'login' %}text-temple-red border-temple-red{% else %}text-gray-600 border-transparent hover:text-temple-red hover:border-gray-300{% endif %}"
|
||||||
|
href="?tab=login">
|
||||||
|
<i data-lucide="user-lock" class="w-4 h-4 inline mr-2"></i>{% trans "User Authentication" %}
|
||||||
|
</a>
|
||||||
|
<a class="px-4 py-2 text-sm font-medium transition border-b-3 {% if active_tab == 'request' %}text-temple-red border-temple-red{% else %}text-gray-600 border-transparent hover:text-temple-red hover:border-gray-300{% endif %}"
|
||||||
|
href="?tab=request">
|
||||||
|
<i data-lucide="globe" class="w-4 h-4 inline mr-2"></i>{% trans "HTTP Requests" %}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="audit-card mx-3">
|
<!-- Tab Content -->
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-sm">
|
||||||
|
|
||||||
<ul class="nav nav-tabs px-3" id="auditTabs" role="tablist">
|
<thead>
|
||||||
<li class="nav-item" role="presentation">
|
{% if active_tab == 'crud' %}
|
||||||
<a class="nav-link nav-link-es {% if active_tab == 'crud' %}active{% endif %}"
|
<tr class="bg-gray-50">
|
||||||
id="crud-tab" href="?tab=crud" aria-controls="crud">
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
|
||||||
<i class="fas fa-database me-2"></i>{% trans "Model Changes (CRUD)" %}
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
|
||||||
</a>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Action" %}</th>
|
||||||
</li>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Model" %}</th>
|
||||||
<li class="nav-item" role="presentation">
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Object PK" %}</th>
|
||||||
<a class="nav-link nav-link-es {% if active_tab == 'login' %}active{% endif %}"
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Changes" %}</th>
|
||||||
id="login-tab" href="?tab=login" aria-controls="login">
|
</tr>
|
||||||
<i class="fas fa-user-lock me-2"></i>{% trans "User Authentication" %}
|
{% elif active_tab == 'login' %}
|
||||||
</a>
|
<tr class="bg-gray-50">
|
||||||
</li>
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
|
||||||
<li class="nav-item" role="presentation">
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
|
||||||
<a class="nav-link nav-link-es {% if active_tab == 'request' %}active{% endif %}"
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Type" %}</th>
|
||||||
id="request-tab" href="?tab=request" aria-controls="request">
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Status" %}</th>
|
||||||
<i class="fas fa-globe me-2"></i>{% trans "HTTP Requests" %}
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "IP Address" %}</th>
|
||||||
</a>
|
</tr>
|
||||||
</li>
|
{% elif active_tab == 'request' %}
|
||||||
</ul>
|
<tr class="bg-gray-50">
|
||||||
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Date/Time" %}</th>
|
||||||
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "User" %}</th>
|
||||||
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Method" %}</th>
|
||||||
|
<th class="px-4 py-3 text-left font-semibold text-gray-700">{% trans "Path" %}</th>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</thead>
|
||||||
|
|
||||||
<div class="tab-content p-4" id="auditTabsContent">
|
<tbody class="divide-y divide-gray-100">
|
||||||
|
{% for log in logs.object_list %}
|
||||||
|
{% if active_tab == 'crud' %}
|
||||||
|
<tr class="hover:bg-gray-50">
|
||||||
|
<td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
|
<td class="px-4 py-3 text-gray-900">{{ log.user.email|default:"N/A" }}</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
{% if log.event_type == 1 %}
|
||||||
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i data-lucide="plus" class="w-3 h-3"></i>{% trans "CREATE" %}</span>
|
||||||
|
{% elif log.event_type == 2 %}
|
||||||
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-yellow-100 text-yellow-800 rounded-full text-xs font-medium"><i data-lucide="edit" class="w-3 h-3"></i>{% trans "UPDATE" %}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="inline-flex items-center gap-1 px-2.5 py-1 bg-red-100 text-red-800 rounded-full text-xs font-medium"><i data-lucide="trash-2" class="w-3 h-3"></i>{% trans "DELETE" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3"><code class="bg-gray-100 px-2 py-1 rounded text-xs">{{ log.content_type.app_label }}.{{ log.content_type.model }}</code></td>
|
||||||
|
<td class="px-4 py-3 text-gray-900">{{ log.object_id }}</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<pre class="bg-gray-100 p-2 rounded text-xs overflow-x-auto max-h-20 overflow-y-auto">{{ log.changed_fields }}</pre>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<div class="tab-pane fade show active" role="tabpanel">
|
{% elif active_tab == 'login' %}
|
||||||
|
<tr class="hover:bg-gray-50">
|
||||||
<div class="table-responsive">
|
<td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
<table class="table table-striped table-hover small">
|
<td class="px-4 py-3 text-gray-900">
|
||||||
|
{% with user_obj=log.user %}
|
||||||
<thead>
|
{% if user_obj %}
|
||||||
{% if active_tab == 'crud' %}
|
{{ user_obj.get_full_name|default:user_obj.username }}
|
||||||
<tr>
|
{% else %}
|
||||||
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th>
|
<span class="text-red-600 font-semibold">{{ log.username|default:"N/A" }}</span>
|
||||||
<th scope="col" style="width: 15%;">{% trans "User" %}</th>
|
{% endif %}
|
||||||
<th scope="col" style="width: 10%;">{% trans "Action" %}</th>
|
{% endwith %}
|
||||||
<th scope="col" style="width: 20%;">{% trans "Model" %}</th>
|
</td>
|
||||||
<th scope="col" style="width: 10%;">{% trans "Object PK" %}</th>
|
<td class="px-4 py-3">
|
||||||
<th scope="col" style="width: 30%;">{% trans "Changes" %}</th>
|
<span class="inline-block px-2.5 py-1 bg-gray-700 text-white rounded-full text-xs font-medium">
|
||||||
</tr>
|
{% if log.login_type == 0 %}{% trans "Login" %}
|
||||||
{% elif active_tab == 'login' %}
|
{% elif log.login_type == 1 %}{% trans "Logout" %}
|
||||||
<tr>
|
{% else %}{% trans "Failed Login" %}{% endif %}
|
||||||
<th scope="col" style="width: 20%;">{% trans "Date/Time" %}</th>
|
</span>
|
||||||
<th scope="col" style="width: 25%;">{% trans "User" %}</th>
|
</td>
|
||||||
<th scope="col" style="width: 15%;">{% trans "Type" %}</th>
|
<td class="px-4 py-3">
|
||||||
<th scope="col" style="width: 10%;">{% trans "Status" %}</th>
|
{% if log.login_type == 2 %}
|
||||||
<th scope="col" style="width: 30%;">{% trans "IP Address" %}</th>
|
<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>
|
||||||
</tr>
|
{% else %}
|
||||||
{% elif active_tab == 'request' %}
|
<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>
|
||||||
<tr>
|
{% endif %}
|
||||||
<th scope="col" style="width: 15%;">{% trans "Date/Time" %}</th>
|
</td>
|
||||||
<th scope="col" style="width: 15%;">{% trans "User" %}</th>
|
<td class="px-4 py-3 text-gray-900">{{ log.remote_ip|default:"Unknown" }}</td>
|
||||||
<th scope="col" style="width: 10%;">{% trans "Method" %}</th>
|
</tr>
|
||||||
<th scope="col" style="width: 45%;">{% trans "Path" %}</th>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
{% for log in logs.object_list %}
|
|
||||||
{% if active_tab == 'crud' %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
|
||||||
<td>{{ log.user.email|default:"N/A" }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge rounded-pill
|
|
||||||
{% if log.event_type == 1 %}badges-crud-create
|
|
||||||
{% elif log.event_type == 2 %}badges-crud-update
|
|
||||||
{% else %}badges-crud-delete{% endif %}">
|
|
||||||
{% if log.event_type == 1 %}<i class="fas fa-plus-circle me-1"></i>{% trans "CREATE" %}
|
|
||||||
{% elif log.event_type == 2 %}<i class="fas fa-edit me-1"></i>{% trans "UPDATE" %}
|
|
||||||
{% else %}<i class="fas fa-trash-alt me-1"></i>{% trans "DELETE" %}{% endif %}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td><code style="color: var(--color-text-dark) !important;">{{ log.content_type.app_label }}.{{ log.content_type.model }}</code></td>
|
|
||||||
<td>{{ log.object_id }}</td>
|
|
||||||
<td>
|
|
||||||
<pre class="p-2 m-0" style="font-size: 0.65rem; max-height: 80px; overflow-y: auto;">{{ log.changed_fields }}</pre>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% elif active_tab == 'login' %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
|
||||||
<td>
|
|
||||||
{% with user_obj=log.user %}
|
|
||||||
{% if user_obj %}
|
|
||||||
{{ user_obj.get_full_name|default:user_obj.username }}
|
|
||||||
{% else %}
|
|
||||||
<span class="text-danger fw-bold">{{ log.username|default:"N/A" }}</span>
|
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge rounded-pill badges-login-status">
|
|
||||||
{% if log.login_type == 0 %}{% trans "Login" %}
|
|
||||||
{% elif log.login_type == 1 %}{% trans "Logout" %}
|
|
||||||
{% else %}{% trans "Failed Login" %}{% endif %}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{% if log.login_type == 2 %}
|
|
||||||
<i class="fas fa-times-circle me-1" style="color: var(--color-danger);"></i>{% trans "Failed" %}
|
|
||||||
{% else %}
|
|
||||||
<i class="fas fa-check-circle me-1" style="color: var(--color-success);"></i>{% trans "Success" %}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td>{{ log.remote_ip|default:"Unknown" }}</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% elif active_tab == 'request' %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
|
||||||
<td>{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge rounded-pill badges-request-method">{{ log.method }}</span>
|
|
||||||
</td>
|
|
||||||
<td><code class="text-break small" style="color: var(--color-text-dark) !important;">{{ log.url}}</code></td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% empty %}
|
|
||||||
<tr><td colspan="6" class="text-center text-muted py-5">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>{% trans "No logs found for this section or the database is empty." %}
|
|
||||||
</td></tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if logs.has_other_pages %}
|
|
||||||
<nav aria-label="Audit Log Pagination" class="pt-3">
|
|
||||||
<ul class="pagination justify-content-end">
|
|
||||||
|
|
||||||
<li class="page-item {% if not logs.has_previous %}disabled{% endif %}">
|
|
||||||
<a class="page-link"
|
|
||||||
href="?tab={{ active_tab }}{% if logs.has_previous %}&page={{ logs.previous_page_number }}{% endif %}"
|
|
||||||
aria-label="Previous">
|
|
||||||
<span aria-hidden="true">«</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
{% for i in logs.paginator.page_range %}
|
|
||||||
{% comment %} Limiting pages displayed around the current page {% endcomment %}
|
|
||||||
{% if i > logs.number|add:'-3' and i < logs.number|add:'3' %}
|
|
||||||
<li class="page-item {% if logs.number == i %}active{% endif %}">
|
|
||||||
<a class="page-link"
|
|
||||||
href="?tab={{ active_tab }}&page={{ i }}">
|
|
||||||
{{ i }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% elif i == logs.number|add:'-3' or i == logs.number|add:'3' %}
|
|
||||||
<li class="page-item disabled"><span class="page-link">...</span></li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<li class="page-item {% if not logs.has_next %}disabled{% endif %}">
|
|
||||||
<a class="page-link"
|
|
||||||
href="?tab={{ active_tab }}{% if logs.has_next %}&page={{ logs.next_page_number }}{% endif %}"
|
|
||||||
aria-label="Next">
|
|
||||||
<span aria-hidden="true">»</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{% elif active_tab == 'request' %}
|
||||||
|
<tr class="hover:bg-gray-50">
|
||||||
|
<td class="px-4 py-3 text-gray-900">{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||||
|
<td class="px-4 py-3 text-gray-900">{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<span class="inline-block px-2.5 py-1 bg-gray-700 text-white rounded-full text-xs font-medium">{{ log.method }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3"><code class="bg-gray-100 px-2 py-1 rounded text-xs break-all">{{ log.url}}</code></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="px-4 py-12 text-center text-gray-500">
|
||||||
|
<i data-lucide="info" class="w-5 h-5 inline mr-2"></i>{% trans "No logs found for this section or database is empty." %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
{% if logs.has_other_pages %}
|
||||||
|
<div class="flex justify-end items-center gap-2 pt-4">
|
||||||
|
<a href="?tab={{ active_tab }}{% if logs.has_previous %}&page={{ logs.previous_page_number }}{% endif %}"
|
||||||
|
class="px-3 py-2 text-sm font-medium rounded-lg {% if logs.has_previous %}bg-white border border-gray-300 text-temple-red hover:bg-gray-50{% else %}bg-gray-100 text-gray-400 cursor-not-allowed{% endif %} transition">
|
||||||
|
<i data-lucide="chevron-left" class="w-4 h-4 inline"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{% for i in logs.paginator.page_range %}
|
||||||
|
{% if i > logs.number|add:'-3' and i < logs.number|add:'3' %}
|
||||||
|
<a href="?tab={{ active_tab }}&page={{ i }}"
|
||||||
|
class="px-3 py-2 text-sm font-medium rounded-lg {% if logs.number == i %}bg-temple-red text-white{% else %}bg-white border border-gray-300 text-temple-red hover:bg-gray-50{% endif %} transition">
|
||||||
|
{{ i }}
|
||||||
|
</a>
|
||||||
|
{% elif i == logs.number|add:'-3' or i == logs.number|add:'3' %}
|
||||||
|
<span class="px-3 py-2 text-sm text-gray-400">...</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<a href="?tab={{ active_tab }}{% if logs.has_next %}&page={{ logs.next_page_number }}{% endif %}"
|
||||||
|
class="px-3 py-2 text-sm font-medium rounded-lg {% if logs.has_next %}bg-white border border-gray-300 text-temple-red hover:bg-gray-50{% else %}bg-gray-100 text-gray-400 cursor-not-allowed{% endif %} transition">
|
||||||
|
<i data-lucide="chevron-right" class="w-4 h-4 inline"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</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,217 +1,181 @@
|
|||||||
{% 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 %}
|
<div class="space-y-2 mb-4" role="alert" aria-live="polite">
|
||||||
<ul class="messages">
|
{% for message in messages %}
|
||||||
{% for message in messages %}
|
<div class="alert {% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-800{% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-800{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-800{% else %}bg-blue-50 border-blue-200 text-blue-800{% endif %} border rounded-lg px-4 py-3 flex items-start justify-between gap-2">
|
||||||
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>
|
<span class="flex-1 text-sm">{{ message }}</span>
|
||||||
{{ message }}
|
<button type="button"
|
||||||
</li>
|
class="text-gray-400 hover:text-gray-600 p-1 shrink-0 touch-target"
|
||||||
{% endfor %}
|
onclick="this.parentElement.remove()"
|
||||||
</ul>
|
aria-label="{% trans 'Dismiss' %}">
|
||||||
{% endif %}
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
<div class="card">
|
</button>
|
||||||
|
|
||||||
<div class="card-body">
|
|
||||||
<form hx-boost="true" method="post" id="email-compose-form" action="{% url 'compose_application_email' job.slug %}"
|
|
||||||
hx-include="#application-form"
|
|
||||||
|
|
||||||
hx-push-url="false"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-on::after-request="new bootstrap.Modal('#emailModal')).hide()">
|
|
||||||
{% csrf_token %}
|
|
||||||
<!-- Recipients Field -->
|
|
||||||
<!-- Recipients Field -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label fw-bold">
|
|
||||||
{% trans "To" %}
|
|
||||||
</label>
|
|
||||||
<div class="border rounded p-3 bg-light" style="max-height: 200px; overflow-y: auto;">
|
|
||||||
|
|
||||||
{# --- 1. DATA LAYER: Render Hidden Inputs for ALL recipients --- #}
|
|
||||||
{# This ensures the backend receives every selected user, not just the visible one #}
|
|
||||||
{% for choice in form.to %}
|
|
||||||
<input type="hidden" name="{{ form.to.name }}" value="{{ choice.data.value }}">
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{# --- 2. VISUAL LAYER: Show only the first one --- #}
|
|
||||||
{# We make it disabled so the user knows they can't deselect it here #}
|
|
||||||
{% for choice in form.to|slice:":1" %}
|
|
||||||
<div class="form-check mb-2">
|
|
||||||
<input class="form-check-input" type="checkbox" checked disabled>
|
|
||||||
<label class="form-check-label">
|
|
||||||
{{ choice.choice_label }}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
{# --- 3. SUMMARY: Show count of hidden recipients --- #}
|
|
||||||
{% if form.to|length > 1 %}
|
|
||||||
<div class="text-muted small mt-2">
|
|
||||||
<i class="fas fa-info-circle me-1"></i>
|
|
||||||
{# Use simple math to show remaining count #}
|
|
||||||
{% with remaining=form.to|length|add:"-1" %}
|
|
||||||
{% blocktrans count total=remaining %}
|
|
||||||
And {{ total }} other recipient
|
|
||||||
{% plural %}
|
|
||||||
And {{ total }} other recipients
|
|
||||||
{% endblocktrans %}
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if form.to.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{% for error in form.to.errors %}
|
|
||||||
<span>{{ error }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Subject Field -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.subject.id_for_label }}" class="form-label fw-bold">
|
|
||||||
{% trans "Subject" %}
|
|
||||||
</label>
|
|
||||||
{{ form.subject }}
|
|
||||||
{% if form.subject.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{% for error in form.subject.errors %}
|
|
||||||
<span>{{ error }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Message Field -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.message.id_for_label }}" class="form-label fw-bold">
|
|
||||||
{% trans "Message" %}
|
|
||||||
</label>
|
|
||||||
{{ form.message }}
|
|
||||||
{% if form.message.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{% for error in form.message.errors %}
|
|
||||||
<span>{{ error }}</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Form Actions -->
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div class="text-muted small">
|
|
||||||
<i class="fas fa-info-circle me-1"></i>
|
|
||||||
{% trans "Email will be sent to all selected recipients" %}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button type="button"
|
|
||||||
class="btn btn-secondary me-2"
|
|
||||||
data-bs-dismiss="modal">
|
|
||||||
<i class="fas fa-times me-1"></i>
|
|
||||||
{% trans "Cancel" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit"
|
|
||||||
class="btn btn-primary"
|
|
||||||
id="send-email-btn">
|
|
||||||
<i class="fas fa-paper-plane me-1"></i>
|
|
||||||
{% trans "Send Email" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Form Card -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
<div class="p-6">
|
||||||
|
<form hx-boost="true" method="post" id="email-compose-form" action="{% url 'compose_application_email' job.slug %}"
|
||||||
|
hx-include="#application-form"
|
||||||
|
hx-push-url="false"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-on::after-request="closeEmailModal()"
|
||||||
|
class="space-y-6">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<!-- Recipients Field -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{% trans "To" %}
|
||||||
|
</label>
|
||||||
|
<div class="border-2 border-gray-300 rounded-lg p-4 bg-gray-50 max-h-48 overflow-y-auto">
|
||||||
|
<!-- Hidden inputs for all recipients -->
|
||||||
|
{% for choice in form.to %}
|
||||||
|
<input type="hidden" name="{{ form.to.name }}" value="{{ choice.data.value }}">
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- Show first recipient only -->
|
||||||
|
{% for choice in form.to|slice:":1" %}
|
||||||
|
<div class="flex items-center gap-2 mb-2">
|
||||||
|
<div class="w-4 h-4 rounded border-2 border-temple-red bg-temple-red flex items-center justify-center">
|
||||||
|
<i data-lucide="check" class="w-3 h-3 text-white"></i>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm text-gray-700">{{ choice.choice_label }}</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<!-- Summary of remaining recipients -->
|
||||||
|
{% if form.to|length > 1 %}
|
||||||
|
<div class="flex items-center gap-2 text-gray-600 text-sm mt-2 pt-2 border-t border-gray-200">
|
||||||
|
<i data-lucide="info" class="w-4 h-4 text-temple-red"></i>
|
||||||
|
{% with remaining=form.to|length|add:"-1" %}
|
||||||
|
{% blocktrans count total=remaining %}
|
||||||
|
And {{ total }} other recipient
|
||||||
|
{% plural %}
|
||||||
|
And {{ total }} other recipients
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if form.to.errors %}
|
||||||
|
<div class="text-red-600 text-sm mt-2">
|
||||||
|
{% for error in form.to.errors %}
|
||||||
|
<span class="block">{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subject Field -->
|
||||||
|
<div>
|
||||||
|
<label for="{{ form.subject.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{% trans "Subject" %}
|
||||||
|
</label>
|
||||||
|
<div class="relative">
|
||||||
|
<input type="text"
|
||||||
|
name="{{ form.subject.name }}"
|
||||||
|
id="{{ form.subject.id_for_label }}"
|
||||||
|
class="w-full px-4 py-2.5 rounded-lg border-2 border-gray-300 focus:border-temple-red focus:ring-2 focus:ring-temple-red/20 focus:outline-none transition text-gray-900 placeholder-gray-400 text-sm"
|
||||||
|
placeholder="{% trans 'Enter email subject...' %}"
|
||||||
|
value="{{ form.subject.value|default:'' }}"
|
||||||
|
required>
|
||||||
|
{% if form.subject.errors %}
|
||||||
|
<div class="text-red-600 text-sm mt-2">
|
||||||
|
{% for error in form.subject.errors %}
|
||||||
|
<span class="block">{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Message Field -->
|
||||||
|
<div>
|
||||||
|
<label for="{{ form.message.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{% trans "Message" %}
|
||||||
|
</label>
|
||||||
|
<div class="relative">
|
||||||
|
<textarea
|
||||||
|
name="{{ form.message.name }}"
|
||||||
|
id="{{ form.message.id_for_label }}"
|
||||||
|
rows="8"
|
||||||
|
class="w-full px-4 py-2.5 rounded-lg border-2 border-gray-300 focus:border-temple-red focus:ring-2 focus:ring-temple-red/20 focus:outline-none transition text-gray-900 placeholder-gray-400 text-sm resize-y"
|
||||||
|
placeholder="{% trans 'Write your message here...' %}"
|
||||||
|
required>{{ form.message.value|default:'' }}</textarea>
|
||||||
|
{% if form.message.errors %}
|
||||||
|
<div class="text-red-600 text-sm mt-2">
|
||||||
|
{% for error in form.message.errors %}
|
||||||
|
<span class="block">{{ error }}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Form Actions -->
|
||||||
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 pt-4 border-t border-gray-200">
|
||||||
|
<div class="flex items-center gap-2 text-gray-600 text-sm">
|
||||||
|
<i data-lucide="info" class="w-4 h-4 text-temple-red"></i>
|
||||||
|
{% trans "Email will be sent to all selected recipients" %}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3 w-full sm:w-auto">
|
||||||
|
<button type="button"
|
||||||
|
class="flex-1 sm:flex-none flex items-center justify-center gap-2 px-6 py-2.5 rounded-lg border border-gray-300 bg-white text-gray-700 hover:bg-gray-50 hover:border-gray-400 transition font-medium"
|
||||||
|
onclick="closeEmailModal()">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
class="flex-1 sm:flex-none flex items-center justify-center gap-2 px-6 py-2.5 rounded-lg bg-temple-red text-white hover:bg-red-700 transition font-medium shadow-sm"
|
||||||
|
id="send-email-btn">
|
||||||
|
<i data-lucide="send" class="w-4 h-4"></i>
|
||||||
|
{% trans "Send Email" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
{{ form.start_date }}
|
<label for="{{ form.start_date.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Date" %}</label>
|
||||||
</div>
|
{{ form.start_date }}
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label for="{{ form.end_date.id_for_label }}">{% trans "End Date" %}</label>
|
|
||||||
{{ form.end_date }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label>{% trans "Working Days" %}</label>
|
|
||||||
{{ form.working_days }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="form-group mb-3">
|
|
||||||
<label for="{{ form.start_time.id_for_label }}">{% trans "Start Time" %}</label>
|
|
||||||
{{ form.start_time }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<div class="form-group mb-3">
|
<label for="{{ form.end_date.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Date" %}</label>
|
||||||
<label for="{{ form.end_time.id_for_label }}">{% trans "End Time" %}</label>
|
{{ form.end_date }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "Working Days" %}</label>
|
||||||
|
{{ form.working_days }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label for="{{ form.start_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "Start Time" %}</label>
|
||||||
|
{{ form.start_time }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="{{ form.end_time.id_for_label }}" class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
|
||||||
{{ form.end_time }}
|
{{ 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 class="col-md-5">
|
|
||||||
<label>{% trans "End Time" %}</label>
|
|
||||||
{{ form.end_time }}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<label> </label><br>
|
|
||||||
{{ form.DELETE }}
|
|
||||||
<button type="button" class="btn btn-danger btn-sm remove-break">{% trans "Remove" %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
<div class="col-span-5">
|
||||||
</div>
|
<label class="block text-sm font-medium text-gray-700 mb-2">{% trans "End Time" %}</label>
|
||||||
<button type="button" id="add-break" class="btn btn-secondary btn-sm mt-2">{% trans "Add Break" %}</button>
|
{{ form.end_time }}
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2 flex items-end">
|
||||||
|
{{ form.DELETE }}
|
||||||
|
<button type="button" class="remove-break w-full bg-red-500 hover:bg-red-600 text-white text-sm font-medium px-4 py-2 rounded-lg transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="trash-2" class="w-4 h-4 inline mr-1"></i>{% trans "Remove" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
<button type="button" id="add-break" class="mt-4 bg-gray-200 hover:bg-gray-300 text-gray-700 text-sm font-medium px-4 py-2 rounded-lg transition">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4 inline mr-1"></i>{% trans "Add Break" %}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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,75 +1,81 @@
|
|||||||
{% 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>
|
||||||
|
<strong class="text-gray-700">{% trans "Job:" %}</strong> {{ access_link.assignment.job.title }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="me-3">
|
|
||||||
<strong>{% trans "Job:" %}</strong> {{ access_link.assignment.job.title }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex align-items-center mb-3">
|
<div class="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 %}
|
||||||
</span>
|
{% trans "Active" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Inactive" %}
|
||||||
|
{% endif %}
|
||||||
|
</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 %}
|
||||||
{% trans "Never" %}
|
{% trans "Never" %}
|
||||||
{% 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>
|
||||||
|
|
||||||
{% comment %} <div class="kaauh-card shadow-sm">
|
|
||||||
<div class="card-body px-3 py-3">
|
|
||||||
<h5 class="card-title mb-3">
|
|
||||||
<i class="fas fa-key me-2 text-warning"></i>
|
|
||||||
{% trans "Access Credentials" %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label text-muted small">{% trans "Login URL" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ request.scheme }}://{{ request.get_host }}{% url 'agency_portal_login' %}"
|
|
||||||
class="form-control font-monospace" id="loginUrl">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('loginUrl')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label text-muted small">{% trans "Access Token" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ access_link.unique_token }}"
|
|
||||||
class="form-control font-monospace" id="accessToken">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessToken')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="form-label text-muted small">{% trans "Password" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ access_link.access_password }}"
|
|
||||||
class="form-control font-monospace" id="accessPassword">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessPassword')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-info">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
|
||||||
{% trans "Share these credentials securely with the agency. They can use this information to log in and submit candidates." %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div> {% endcomment %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<!-- Sidebar -->
|
||||||
<div class="kaauh-card shadow-sm mb-4 px-3 py-3">
|
<div class="lg:col-span-1 space-y-6">
|
||||||
<div class="card-body">
|
<!-- Usage Statistics -->
|
||||||
<h5 class="card-title mb-3">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<i class="fas fa-chart-line me-2 text-info"></i>
|
<div class="p-6">
|
||||||
|
<h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="trending-up" class="w-5 h-5 text-blue-600"></i>
|
||||||
|
</div>
|
||||||
{% trans "Usage Statistics" %}
|
{% 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="mt-4">
|
||||||
<div class="progress-bar {% if progress_percent >= 80 %}bg-danger{% elif progress_percent >= 60 %}bg-warning{% else %}bg-success{% endif %}"
|
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||||
style="width: {{ progress_percent }}%"></div>
|
<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,379 +3,255 @@
|
|||||||
|
|
||||||
{% 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">
|
||||||
{% trans "Assignment Details" %}
|
<i data-lucide="info" class="w-5 h-5"></i>
|
||||||
</h5>
|
{% trans "Assignment Details" %}
|
||||||
|
</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>
|
||||||
|
{% if assignment.is_expired %}
|
||||||
|
<span class="text-sm text-red-600 flex items-center gap-1 mt-1">
|
||||||
|
<i data-lucide="alert-triangle" class="w-4 h-4"></i>
|
||||||
|
{% trans "Expired" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if assignment.is_expired %}
|
|
||||||
<small class="text-danger">
|
|
||||||
<i class="fas fa-exclamation-triangle me-1"></i>{% trans "Expired" %}
|
|
||||||
</small>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if assignment.admin_notes %}
|
|
||||||
<div class="mt-3 pt-3 border-top">
|
|
||||||
<label class="text-muted small">{% trans "Admin Notes" %}</label>
|
|
||||||
<div class="text-muted">{{ assignment.admin_notes }}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% comment %} <div class="kaauh-card shadow-sm mb-4">
|
|
||||||
<div class="card-body my-2">
|
|
||||||
<h5 class="card-title mb-3 mx-2">
|
|
||||||
<i class="fas fa-key me-2 text-warning"></i>
|
|
||||||
{% trans "Access Credentials" %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="mb-3 mx-2">
|
|
||||||
<label class="form-label text-muted small">{% trans "Login URL" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ request.scheme }}://{{ request.get_host }}{% url 'agency_portal_login' %}"
|
|
||||||
class="form-control font-monospace" id="loginUrl">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('loginUrl')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3 mx-2">
|
{% if assignment.admin_notes %}
|
||||||
<label class="form-label text-muted small">{% trans "Access Token" %}</label>
|
<div class="mt-6 pt-4 border-t border-gray-200">
|
||||||
<div class="input-group">
|
<label class="text-sm text-gray-500 font-medium block mb-2">{% trans "Admin Notes" %}</label>
|
||||||
<input type="text" readonly value="{{ access_link.unique_token }}"
|
<div class="text-gray-700">{{ assignment.admin_notes }}</div>
|
||||||
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>
|
|
||||||
|
|
||||||
<div class="mb-3 mx-2">
|
|
||||||
<label class="form-label text-muted small">{% trans "Password" %}</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" readonly value="{{ access_link.access_password }}"
|
|
||||||
class="form-control font-monospace" id="accessPassword">
|
|
||||||
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('accessPassword')">
|
|
||||||
<i class="fas fa-copy"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="alert alert-info mx-2">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
|
||||||
{% trans "Share these credentials securely with the agency. They can use this information to log in and submit applications." %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if access_link %}
|
|
||||||
<a href="{% url 'agency_access_link_detail' access_link.slug %}"
|
|
||||||
class="btn btn-outline-info btn-sm mx-2">
|
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Access Links Details" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div> {% endcomment %}
|
|
||||||
|
|
||||||
<!-- Applications Card -->
|
|
||||||
<div class="kaauh-card p-4">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
|
||||||
<i class="fas fa-users me-2"></i>
|
|
||||||
{% trans "Submitted Applications" %} ({{ total_applications }})
|
|
||||||
</h5>
|
|
||||||
{% if access_link %}
|
|
||||||
<a href="{% url 'agency_portal_login' %}" target="_blank" class="btn btn-outline-info btn-sm">
|
|
||||||
<i class="fas fa-external-link-alt me-1"></i> {% trans "Preview Portal" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if applications %}
|
<!-- Applications Card -->
|
||||||
<div class="table-responsive">
|
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
||||||
<table class="table table-hover">
|
<div class="p-6">
|
||||||
<thead>
|
<div class="flex justify-between items-center mb-4">
|
||||||
<tr>
|
<h5 class="text-lg font-semibold text-temple-dark flex items-center gap-2">
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Application"%}</th>
|
<i data-lucide="users" class="w-5 h-5"></i>
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Contact" %}
|
{% trans "Submitted Applications" %} ({{ total_applications }})
|
||||||
</th>
|
</h5>
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Stage" %}
|
{% if access_link %}
|
||||||
</th>
|
<a href="{% url 'agency_portal_login' %}" target="_blank"
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted">{% trans "Submitted"%}</th>
|
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">
|
||||||
<th class="px-4 py-3 text-uppercase small fw-bold text-muted text-end">{% trans "Actions" %}</th>
|
<i data-lucide="external-link" class="w-4 h-4"></i>
|
||||||
</tr>
|
<span class="hidden sm:inline">{% trans "Preview Portal" %}</span>
|
||||||
</thead>
|
</a>
|
||||||
<tbody>
|
{% endif %}
|
||||||
{% for application in applications %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fw-bold">{{ application.name }}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="small">
|
|
||||||
<div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div>
|
|
||||||
<div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="small text-muted">
|
|
||||||
<div class="mb-1"><i class="fas fa-envelope me-2 w-20"></i>
|
|
||||||
{{application.email }}</div>
|
|
||||||
<div><i class="fas fa-phone me-2 w-20"></i>{{ application.phone }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td class="px-4">
|
|
||||||
<span class="badge bg-primary-theme px-3">
|
|
||||||
{{application.get_stage_display }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4">
|
|
||||||
<span class="small text-muted">{{ application.created_at|date:"M d, Y" }}</span>
|
|
||||||
</td>
|
|
||||||
<td class="px-4 text-end">
|
|
||||||
<a href="{% url 'application_detail' application.slug %}"
|
|
||||||
class="btn btn-sm btn-outline-primary" title="{% trans 'View Details' %}">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
|
||||||
<div class="text-center py-4">
|
{% if applications %}
|
||||||
<i class="fas fa-users fa-2x text-muted mb-3"></i>
|
<div class="overflow-x-auto">
|
||||||
<h6 class="text-muted">{% trans "No applications submitted yet" %}</h6>
|
<table class="w-full">
|
||||||
<p class="text-muted small">
|
<thead>
|
||||||
{% trans "Applications will appear here once the agency submits them through their portal." %}
|
<tr class="border-b border-gray-200">
|
||||||
</p>
|
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
</div>
|
{% trans "Application" %}
|
||||||
{% endif %}
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
|
{% trans "Contact" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
|
{% trans "Stage" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
|
{% trans "Submitted" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-semibold text-gray-500 uppercase tracking-wider">
|
||||||
|
{% trans "Actions" %}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-gray-200">
|
||||||
|
{% for application in applications %}
|
||||||
|
<tr class="hover:bg-gray-50 transition">
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<div class="font-semibold text-gray-900">{{ application.name }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<div class="text-sm text-gray-600 space-y-1">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<i data-lucide="mail" class="w-3 h-3"></i>
|
||||||
|
{{ application.email }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<i data-lucide="phone" class="w-3 h-3"></i>
|
||||||
|
{{ application.phone }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-temple-red text-white">
|
||||||
|
{{ application.get_stage_display }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<span class="text-sm text-gray-600">{{ application.created_at|date:"M d, Y" }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-right">
|
||||||
|
<a href="{% url 'application_detail' application.slug %}"
|
||||||
|
class="inline-flex items-center justify-center p-2 text-temple-red hover:bg-red-50 rounded-lg transition"
|
||||||
|
title="{% trans 'View Details' %}">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="text-center py-12">
|
||||||
|
<i data-lucide="users" class="w-16 h-16 text-gray-400 mx-auto mb-4"></i>
|
||||||
|
<h6 class="text-lg font-semibold text-gray-600 mb-2">{% trans "No applications submitted yet" %}</h6>
|
||||||
|
<p class="text-gray-500 text-sm">
|
||||||
|
{% trans "Applications will appear here once agency submits them through their portal." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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="w-full bg-gray-200 rounded-full h-2">
|
||||||
<div class="progress-bar bg-primary-theme" style="width: {{ progress }}%"></div>
|
<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 class="small text-muted mb-2">
|
|
||||||
{% trans "From" %}: {{ message.sender.get_full_name }}
|
|
||||||
</div>
|
|
||||||
<div class="small">{{ message.message|truncatewords:30 }}</div>
|
|
||||||
{% if not message.is_read %}
|
|
||||||
<span class="badge bg-info mt-2">{% trans "New" %}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-sm text-gray-600 mb-2">
|
||||||
|
{% trans "From" %}: {{ message.sender.get_full_name }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-700">{{ message.message|truncatewords:30 }}</div>
|
||||||
|
{% if not message.is_read %}
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mt-2">
|
||||||
|
{% trans "New" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</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,56 +298,108 @@
|
|||||||
</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">
|
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
||||||
{% trans "Cancel Assignment" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="alert alert-warning mb-3">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>
|
|
||||||
<strong>{% trans "Warning:" %}</strong>
|
|
||||||
{% trans "This action cannot be undone. The agency will no longer be able to submit applications." %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card mb-3">
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full">
|
||||||
<div class="card-body bg-light">
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<h6 class="card-title text-primary mb-0">
|
<div class="sm:flex sm:items-start">
|
||||||
<i class="fas fa-building me-2"></i>
|
<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">
|
||||||
{{ assignment.agency.name }}
|
<i data-lucide="alert-triangle" class="w-6 h-6 text-red-600"></i>
|
||||||
</h6>
|
</div>
|
||||||
<p class="card-text text-muted mb-0">
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
|
||||||
<i class="fas fa-briefcase me-2"></i>
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">
|
||||||
{{ assignment.job.title }}
|
{% trans "Cancel Assignment" %}
|
||||||
|
</h3>
|
||||||
|
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-4">
|
||||||
|
<p class="text-sm text-yellow-800">
|
||||||
|
<strong>{% trans "Warning:" %}</strong>
|
||||||
|
{% trans "This action cannot be undone. The agency will no longer be able to submit applications." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
||||||
|
<h6 class="text-temple-red font-semibold mb-2 flex items-center gap-2">
|
||||||
|
<i data-lucide="building" class="w-4 h-4"></i>
|
||||||
|
{{ assignment.agency.name }}
|
||||||
|
</h6>
|
||||||
|
<p class="text-gray-600 text-sm flex items-center gap-2">
|
||||||
|
<i data-lucide="briefcase" class="w-4 h-4"></i>
|
||||||
|
{{ assignment.job.title }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'agency_assignment_cancel' assignment.slug %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="cancel_reason" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
<i data-lucide="message-square" class="w-4 h-4 inline mr-1"></i>
|
||||||
|
{% trans "Cancellation Reason" %}
|
||||||
|
<span class="font-normal text-gray-500">({% trans "Optional" %})</span>
|
||||||
|
</label>
|
||||||
|
<textarea class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900"
|
||||||
|
id="cancel_reason" name="cancel_reason" rows="4"
|
||||||
|
placeholder="{% trans 'Enter reason for cancelling this assignment (optional)...' %}"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-3">
|
||||||
|
<button type="button"
|
||||||
|
class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition"
|
||||||
|
onclick="document.getElementById('cancelAssignmentModal').classList.add('hidden')">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="x-circle" class="w-4 h-4"></i>
|
||||||
|
{% trans "Cancel Assignment" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Extend Deadline Modal -->
|
||||||
|
<div id="extendDeadlineModal" class="hidden fixed inset-0 z-50 overflow-y-auto">
|
||||||
|
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
|
||||||
|
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('extendDeadlineModal').classList.add('hidden')"></div>
|
||||||
|
|
||||||
|
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg w-full">
|
||||||
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<i data-lucide="clock" class="w-5 h-5"></i>
|
||||||
|
{% trans "Extend Assignment Deadline" %}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'agency_assignment_extend_deadline' assignment.slug %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="mb-4">
|
||||||
|
<label for="new_deadline" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{% trans "New Deadline" %} <span class="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
|
<input type="datetime-local"
|
||||||
|
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition text-gray-900"
|
||||||
|
id="new_deadline" name="new_deadline" required>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">
|
||||||
|
{% trans "Current deadline:" %} {{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="post" action="{% url 'agency_assignment_cancel' assignment.slug %}">
|
<div class="flex justify-end gap-3">
|
||||||
{% csrf_token %}
|
<button type="button"
|
||||||
<div class="mb-3">
|
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"
|
||||||
<label for="cancel_reason" class="form-label fw-bold">
|
onclick="document.getElementById('extendDeadlineModal').classList.add('hidden')">
|
||||||
<i class="fas fa-comment-alt me-2"></i>
|
|
||||||
{% trans "Cancellation Reason" %}
|
|
||||||
<span class="text-muted fw-normal">({% trans "Optional" %})</span>
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="cancel_reason" name="cancel_reason" rows="4"
|
|
||||||
placeholder="{% trans 'Enter reason for cancelling this assignment (optional)...' %}"></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<a href="{% url 'agency_assignment_detail' assignment.slug %}" class="btn btn-secondary">
|
|
||||||
<i class="fas fa-arrow-left me-1"></i>
|
|
||||||
{% 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-temple-red hover:bg-red-800 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
{% trans "Cancel Assignment" %}
|
<i data-lucide="clock" class="w-4 h-4"></i>
|
||||||
|
{% trans "Extend Deadline" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -479,107 +407,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Extend Deadline Modal -->
|
|
||||||
<div class="modal fade" id="extendDeadlineModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
|
||||||
<i class="fas fa-clock me-2"></i>
|
|
||||||
{% trans "Extend Assignment Deadline" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="{% url 'agency_assignment_extend_deadline' assignment.slug %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="new_deadline" class="form-label">
|
|
||||||
{% trans "New Deadline" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="datetime-local" class="form-control" id="new_deadline"
|
|
||||||
name="new_deadline" required>
|
|
||||||
<small class="form-text text-muted">
|
|
||||||
{% trans "Current deadline:" %} {{ assignment.deadline_date|date:"Y-m-d H:i" }}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
||||||
{% trans "Cancel" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-clock me-1"></i> {% trans "Extend Deadline" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
{% 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 -->
|
|
||||||
<nav aria-label="breadcrumb">
|
|
||||||
<ol class="breadcrumb">
|
|
||||||
<li class="breadcrumb-item">
|
|
||||||
<a href="{% url 'agency_list' %}" class="text-decoration-none text-secondary">
|
|
||||||
<i class="fas fa-building me-1"></i> {% trans "Agencies" %}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% if agency %}
|
|
||||||
<li class="breadcrumb-item">
|
|
||||||
<a href="{% url 'agency_detail' agency.slug %}" class="text-decoration-none text-secondary">
|
|
||||||
{{ agency.name }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li class="breadcrumb-item active" aria-current="page"
|
|
||||||
style="
|
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;">{% trans "Update" %}</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="breadcrumb-item active" aria-current="page"
|
|
||||||
style="
|
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
|
||||||
font-weight: 600;">{% trans "Create" %}</li>
|
|
||||||
{% endif %}
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header Card with Gradient Background -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="bg-gradient-to-r from-temple-red to-[#7a1a29] rounded-2xl shadow-lg p-6 md:p-8 text-white">
|
||||||
<h6 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||||
<i class="fas fa-building me-2"></i> {{ title }}
|
<div class="flex-1">
|
||||||
</h6>
|
<h1 class="text-2xl md:text-3xl font-bold mb-2 flex items-center gap-3">
|
||||||
<div class="d-flex gap-2">
|
<i data-lucide="building" class="w-8 h-8"></i>
|
||||||
|
{{ title }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-white/80 text-sm md:text-base">
|
||||||
|
{% if agency %}
|
||||||
|
{% trans "Update agency information" %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Enter details to create a new agency." %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
{% if agency %}
|
{% if agency %}
|
||||||
<a href="{% url 'agency_detail' agency.slug %}" class="btn btn-outline-secondary">
|
<a href="{% url 'agency_detail' agency.slug %}"
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
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>
|
||||||
<a href="{% url 'agency_delete' agency.slug %}" class="btn btn-danger">
|
<a href="{% url 'agency_delete' agency.slug %}"
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
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>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'agency_list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'agency_list' %}"
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to 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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if agency %}
|
{% if agency %}
|
||||||
<!-- Current Agency Info -->
|
<!-- Current Agency Info Card -->
|
||||||
<div class="card shadow-sm mb-4">
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-6">
|
||||||
<div class="card-body">
|
<div class="bg-temple-cream rounded-lg p-4 border-l-4 border-temple-red">
|
||||||
<div class="current-profile">
|
<h6 class="text-temple-dark font-bold mb-3 flex items-center gap-2">
|
||||||
<h6><i class="fas fa-info-circle me-2"></i>{% trans "Currently Editing" %}</h6>
|
<i data-lucide="info" class="w-4 h-4"></i>
|
||||||
<div class="d-flex align-items-center">
|
{% trans "Currently Editing" %}
|
||||||
|
</h6>
|
||||||
<div>
|
<div class="space-y-2">
|
||||||
<h5 class="mb-1">{{ agency.name }}</h5>
|
<h5 class="text-lg font-semibold text-gray-900">{{ 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 %}
|
|
||||||
{% if agency.email %}
|
|
||||||
<p class="text-muted mb-0">{{ agency.email }}</p>
|
|
||||||
{% endif %}
|
|
||||||
<small class="text-muted">
|
|
||||||
{% trans "Created" %}: {{ agency.created_at|date:"d M Y" }} •
|
|
||||||
{% trans "Last Updated" %}: {{ agency.updated_at|date:"d M Y" }}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Form Card -->
|
|
||||||
<div class="card shadow-sm">
|
|
||||||
<div class="card-body p-4">
|
|
||||||
{% if form.non_field_errors %}
|
|
||||||
<div class="alert alert-danger" role="alert">
|
|
||||||
<h5 class="alert-heading">
|
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>{% trans "Error" %}
|
|
||||||
</h5>
|
|
||||||
{% for error in form.non_field_errors %}
|
|
||||||
<p class="mb-0">{{ error }}</p>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if agency.email %}
|
||||||
{% if messages %}
|
<p class="text-gray-600 text-sm">{{ agency.email }}</p>
|
||||||
{% 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 %}
|
{% endif %}
|
||||||
|
<p class="text-gray-500 text-xs">
|
||||||
<form method="post" novalidate id="agency-form">
|
{% trans "Created" %}: {{ agency.created_at|date:"d M Y" }} •
|
||||||
{% csrf_token %}
|
{% trans "Last Updated" %}: {{ agency.updated_at|date:"d M Y" }}
|
||||||
|
</p>
|
||||||
<!-- Name -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.name.id_for_label }}" class="form-label">
|
|
||||||
{{ form.name.label }} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.name|add_class:"form-control" }}
|
|
||||||
{% if form.name.errors %}
|
|
||||||
{% for error in form.name.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.name.help_text %}
|
|
||||||
<div class="form-text">{{ form.name.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Contact Person and Phone -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.contact_person.id_for_label }}" class="form-label">
|
|
||||||
{{ form.contact_person.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.contact_person|add_class:"form-control" }}
|
|
||||||
{% if form.contact_person.errors %}
|
|
||||||
{% for error in form.contact_person.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.contact_person.help_text %}
|
|
||||||
<div class="form-text">{{ form.contact_person.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
|
||||||
{{ form.phone.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.phone|add_class:"form-control" }}
|
|
||||||
{% if form.phone.errors %}
|
|
||||||
{% for error in form.phone.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.phone.help_text %}
|
|
||||||
<div class="form-text">{{ form.phone.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Email and Website -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.email.id_for_label }}" class="form-label">
|
|
||||||
{{ form.email.label }}<span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.email|add_class:"form-control" }}
|
|
||||||
{% if form.email.errors %}
|
|
||||||
{% for error in form.email.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.email.help_text %}
|
|
||||||
<div class="form-text">{{ form.email.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.website.id_for_label }}" class="form-label">
|
|
||||||
{{ form.website.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.website|add_class:"form-control" }}
|
|
||||||
{% if form.website.errors %}
|
|
||||||
{% for error in form.website.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.website.help_text %}
|
|
||||||
<div class="form-text">{{ form.website.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Address -->
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.address.id_for_label }}" class="form-label">
|
|
||||||
{{ form.address.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.address|add_class:"form-control" }}
|
|
||||||
{% if form.address.errors %}
|
|
||||||
{% for error in form.address.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.address.help_text %}
|
|
||||||
<div class="form-text">{{ form.address.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Country and City -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.country.id_for_label }}" class="form-label">
|
|
||||||
{{ form.country.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.country|add_class:"form-control" }}
|
|
||||||
{% if form.country.errors %}
|
|
||||||
{% for error in form.country.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.country.help_text %}
|
|
||||||
<div class="form-text">{{ form.country.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.city.id_for_label }}" class="form-label">
|
|
||||||
{{ form.city.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.city|add_class:"form-control" }}
|
|
||||||
{% if form.city.errors %}
|
|
||||||
{% for error in form.city.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.city.help_text %}
|
|
||||||
<div class="form-text">{{ form.city.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Description -->
|
|
||||||
<div class="mb-4">
|
|
||||||
<label for="{{ form.description.id_for_label }}" class="form-label">
|
|
||||||
{{ form.description.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.description|add_class:"form-control" }}
|
|
||||||
{% if form.description.errors %}
|
|
||||||
{% for error in form.description.errors %}
|
|
||||||
<div class="invalid-feedback d-block">{{ error }}</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
{% if form.description.help_text %}
|
|
||||||
<div class="form-text">{{ form.description.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<button form="agency-form" type="submit" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-save me-1"></i> {{ button_text }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endblock %}
|
|
||||||
|
<!-- Form Card -->
|
||||||
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
|
||||||
|
<div class="border-b border-gray-200 px-6 py-4 bg-gray-50 rounded-t-xl">
|
||||||
|
<h2 class="text-lg font-bold text-temple-red flex items-center gap-2">
|
||||||
|
<i data-lucide="file-text" class="w-5 h-5"></i>
|
||||||
|
{% trans "Agency Information" %}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-6 md:p-8">
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<div class="bg-red-50 border border-red-200 rounded-xl p-4 mb-6" role="alert">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<i data-lucide="alert-circle" class="w-5 h-5 text-red-600 shrink-0 mt-0.5"></i>
|
||||||
|
<div>
|
||||||
|
<h5 class="font-bold text-red-800 mb-1">{% trans "Error" %}</h5>
|
||||||
|
{% for error in form.non_field_errors %}
|
||||||
|
<p class="text-red-700 text-sm">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" novalidate id="agency-form" class="space-y-6">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<!-- Two Column Form Layout -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<!-- Name -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.name.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.name.label }} <span class="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
name="name"
|
||||||
|
id="{{ form.name.id_for_label }}"
|
||||||
|
value="{{ form.name.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Agency Name' %}"
|
||||||
|
{% if form.name.field.required %}required{% endif %}>
|
||||||
|
{% if form.name.errors %}
|
||||||
|
{% for error in form.name.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.name.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.name.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Person -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.contact_person.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.contact_person.label }}
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
name="contact_person"
|
||||||
|
id="{{ form.contact_person.id_for_label }}"
|
||||||
|
value="{{ form.contact_person.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Contact Person' %}">
|
||||||
|
{% if form.contact_person.errors %}
|
||||||
|
{% for error in form.contact_person.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.contact_person.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.contact_person.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Phone -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.phone.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.phone.label }}
|
||||||
|
</label>
|
||||||
|
<input type="tel"
|
||||||
|
name="phone"
|
||||||
|
id="{{ form.phone.id_for_label }}"
|
||||||
|
value="{{ form.phone.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Phone Number' %}">
|
||||||
|
{% if form.phone.errors %}
|
||||||
|
{% for error in form.phone.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.phone.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.phone.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.email.label }} <span class="text-red-600">*</span>
|
||||||
|
</label>
|
||||||
|
<input type="email"
|
||||||
|
name="email"
|
||||||
|
id="{{ form.email.id_for_label }}"
|
||||||
|
value="{{ form.email.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Email Address' %}"
|
||||||
|
{% if form.email.field.required %}required{% endif %}>
|
||||||
|
{% if form.email.errors %}
|
||||||
|
{% for error in form.email.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.email.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.email.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Website -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.website.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.website.label }}
|
||||||
|
</label>
|
||||||
|
<input type="url"
|
||||||
|
name="website"
|
||||||
|
id="{{ form.website.id_for_label }}"
|
||||||
|
value="{{ form.website.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Website URL' %}">
|
||||||
|
{% if form.website.errors %}
|
||||||
|
{% for error in form.website.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.website.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.website.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Country -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.country.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.country.label }}
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
name="country"
|
||||||
|
id="{{ form.country.id_for_label }}"
|
||||||
|
value="{{ form.country.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'Country' %}">
|
||||||
|
{% if form.country.errors %}
|
||||||
|
{% for error in form.country.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.country.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.country.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- City -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.city.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.city.label }}
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
name="city"
|
||||||
|
id="{{ form.city.id_for_label }}"
|
||||||
|
value="{{ form.city.value|default:'' }}"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm"
|
||||||
|
placeholder="{% trans 'City' %}">
|
||||||
|
{% if form.city.errors %}
|
||||||
|
{% for error in form.city.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.city.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.city.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Address (Full Width) -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.address.label }}
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
name="address"
|
||||||
|
id="{{ form.address.id_for_label }}"
|
||||||
|
rows="3"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm resize-none"
|
||||||
|
placeholder="{% trans 'Street Address' %}">{{ form.address.value|default:'' }}</textarea>
|
||||||
|
{% if form.address.errors %}
|
||||||
|
{% for error in form.address.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.address.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.address.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Description (Full Width) -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<label for="{{ form.description.id_for_label }}" class="block text-sm font-semibold text-gray-700">
|
||||||
|
{{ form.description.label }}
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
name="description"
|
||||||
|
id="{{ form.description.id_for_label }}"
|
||||||
|
rows="4"
|
||||||
|
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition text-sm resize-none"
|
||||||
|
placeholder="{% trans 'Agency Description' %}">{{ form.description.value|default:'' }}</textarea>
|
||||||
|
{% if form.description.errors %}
|
||||||
|
{% for error in form.description.errors %}
|
||||||
|
<p class="text-red-600 text-xs mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-3 h-3"></i>
|
||||||
|
{{ error }}
|
||||||
|
</p>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% if form.description.help_text %}
|
||||||
|
<p class="text-gray-500 text-xs">{{ form.description.help_text }}</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit Button -->
|
||||||
|
<div class="pt-4 border-t border-gray-200">
|
||||||
|
<button type="submit"
|
||||||
|
class="w-full sm:w-auto inline-flex items-center justify-center gap-2 px-8 py-3 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold rounded-lg text-sm transition shadow-md hover:shadow-lg transform hover:-translate-y-0.5 touch-target">
|
||||||
|
<i data-lucide="save" class="w-4 h-4"></i>
|
||||||
|
{{ button_text }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
<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,694 +3,307 @@
|
|||||||
|
|
||||||
{% 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 href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition shadow-sm hover:shadow-md {% if assignment.is_full %}opacity-50 cursor-not-allowed{% endif %}">
|
||||||
|
<i data-lucide="user-plus" class="w-4 h-4"></i> {% trans "Submit New application" %}
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="btn btn-sm btn-main-action {% if assignment.is_full %}disabled{% endif %}" >
|
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit New application" %}
|
|
||||||
</a>
|
|
||||||
{% comment %} <a href="#" class="btn btn-outline-info">
|
|
||||||
<i class="fas fa-envelope me-1"></i> {% trans "Messages" %}
|
|
||||||
{% if total_unread_messages > 0 %}
|
|
||||||
<span class="badge bg-danger ms-1">{{ total_unread_messages }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</a> {% endcomment %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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">
|
||||||
{% trans "Assignment Details" %}
|
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
|
||||||
</h5>
|
<i data-lucide="info" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Assignment Details" %}
|
||||||
|
</h5>
|
||||||
|
|
||||||
<div class="row">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
<div class="mb-3">
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Job Title" %}</label>
|
||||||
<label class="text-muted small">{% trans "Job Title" %}</label>
|
<div class="font-bold text-gray-900">{{ assignment.job.title }}</div>
|
||||||
<div class="fw-bold">{{ assignment.job.title }}</div>
|
<div class="text-sm text-gray-600">{{ assignment.job.department }}</div>
|
||||||
<div class="text-muted small">{{ assignment.job.department }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div>
|
||||||
<label class="text-muted small">{% trans "Status" %}</label>
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Status" %}</label>
|
||||||
<div>
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
||||||
<span class="status-badge status-{{ assignment.status }}" >
|
{{ assignment.get_status_display }}
|
||||||
{{ assignment.get_status_display }}
|
</span>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div class="col-md-6">
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Deadline" %}</label>
|
||||||
<div class="mb-3">
|
<div class="{% if assignment.is_expired %}text-red-600{% else %}text-gray-700{% endif %}">
|
||||||
<label class="text-muted small">{% trans "Deadline" %}</label>
|
<i data-lucide="calendar" class="w-4 h-4 inline mr-1"></i>
|
||||||
<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>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Actions Card -->
|
|
||||||
{% comment %} <div class="kaauh-card p-4 mb-4">
|
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
|
||||||
<i class="fas fa-bolt me-2"></i>
|
|
||||||
{% trans "Quick Actions" %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="d-grid gap-2">
|
|
||||||
{% if assignment.can_submit %}
|
|
||||||
<a href="{% url 'agency_portal_submit_application_page' assignment.slug %}" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit New application" %}
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<button class="btn btn-outline-secondary" disabled>
|
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Cannot Submit applications" %}
|
|
||||||
</button>
|
|
||||||
<div class="alert alert-warning mt-2">
|
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
||||||
{% if assignment.is_expired %}
|
|
||||||
{% trans "This assignment has expired. Submissions are no longer accepted." %}
|
|
||||||
{% elif assignment.is_full %}
|
|
||||||
{% trans "Maximum application limit reached for this assignment." %}
|
|
||||||
{% else %}
|
|
||||||
{% trans "This assignment is not currently active." %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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>
|
</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">
|
||||||
{% trans "Submitted applications" %} ({{ total_applications }})
|
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
|
||||||
</h5>
|
<i data-lucide="users" class="w-5 h-5 text-temple-red"></i>
|
||||||
<span class="badge bg-primary-theme">{{ total_applications }}/{{ max_applications }}</span>
|
</div>
|
||||||
</div>
|
{% trans "Submitted applications" %} ({{ total_applications }})
|
||||||
|
</h5>
|
||||||
{% if page_obj %}
|
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-temple-red/10 text-temple-red">
|
||||||
<div class="table-responsive">
|
{{ total_applications }}/{{ max_applications }}
|
||||||
<table class="table table-hover">
|
</span>
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{% trans "Name" %}</th>
|
|
||||||
<th>{% trans "Contact" %}</th>
|
|
||||||
<th>{% trans "Stage" %}</th>
|
|
||||||
<th>{% trans "Submitted" %}</th>
|
|
||||||
<th>{% trans "Actions" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for application in page_obj %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div class="fw-bold">{{ application.name }}</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="small">
|
|
||||||
<div><i class="fas fa-envelope me-1"></i> {{ application.email }}</div>
|
|
||||||
<div><i class="fas fa-phone me-1"></i> {{ application.phone }}</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="small text-muted">
|
|
||||||
{{ application.created_at|date:"Y-m-d H:i" }}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="{% url 'applicant_application_detail' application.slug %}" class="btn btn-sm btn-outline-primary" title="{% trans 'View Profile' %}">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
{% if page_obj %}
|
||||||
{% if page_obj.has_other_pages %}
|
<div class="overflow-x-auto">
|
||||||
<nav aria-label="Candidate pagination">
|
<table class="w-full">
|
||||||
<ul class="pagination justify-content-center">
|
<thead>
|
||||||
|
<tr class="border-b border-gray-200">
|
||||||
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Name" %}</th>
|
||||||
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Contact" %}</th>
|
||||||
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Stage" %}</th>
|
||||||
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Submitted" %}</th>
|
||||||
|
<th class="text-left py-3 px-4 text-sm font-semibold text-gray-700">{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for application in page_obj %}
|
||||||
|
<tr class="border-b border-gray-100 hover:bg-gray-50 transition">
|
||||||
|
<td class="py-3 px-4">
|
||||||
|
<div class="font-semibold text-gray-900">{{ application.name }}</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-4">
|
||||||
|
<div class="text-sm text-gray-600">
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<i data-lucide="mail" class="w-3 h-3"></i> {{ application.email }}
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<i data-lucide="phone" class="w-3 h-3"></i> {{ application.phone }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-4">
|
||||||
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-temple-red/10 text-temple-red">
|
||||||
|
{{ application.get_stage_display }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-4">
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
{{ application.created_at|date:"Y-m-d H:i" }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-4">
|
||||||
|
<a href="{% url 'applicant_application_detail' application.slug %}" class="inline-flex items-center justify-center w-8 h-8 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition" title="{% trans 'View Profile' %}">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
{% if page_obj.has_other_pages %}
|
||||||
|
<div class="flex justify-center items-center gap-2 mt-6">
|
||||||
{% if page_obj.has_previous %}
|
{% 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 %}
|
||||||
|
{% else %}
|
||||||
|
<div class="text-center py-8">
|
||||||
|
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<i data-lucide="users" class="w-8 h-8 text-gray-400"></i>
|
||||||
|
</div>
|
||||||
|
<h6 class="text-gray-600 mb-2">{% trans "No applications submitted yet" %}</h6>
|
||||||
|
<p class="text-gray-500 text-sm">
|
||||||
|
{% trans "Submit applications using form above to get started." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
</div>
|
||||||
<div class="text-center py-4">
|
|
||||||
<i class="fas fa-users fa-2x text-muted mb-3"></i>
|
|
||||||
<h6 class="text-muted">{% trans "No applications submitted yet" %}</h6>
|
|
||||||
<p class="text-muted small">
|
|
||||||
{% trans "Submit applications using the form above to get started." %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
</div>
|
<div class="lg:col-span-1 space-y-6">
|
||||||
<div class="col-lg-4 col-md-12">
|
<!-- Progress Card -->
|
||||||
<!-- Progress Card -->
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<div class="col kaauh-card p-4 mb-4">
|
<div class="p-6">
|
||||||
<h5 class="mb-4 text-center" style="color: var(--kaauh-teal-dark);">
|
<h5 class="text-xl font-bold text-gray-900 mb-4 text-center">{% trans "Submission Progress" %}</h5>
|
||||||
{% trans "Submission Progress" %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="text-center mb-3">
|
<div class="text-center mb-4">
|
||||||
<div class="progress-ring">
|
<div class="relative w-32 h-32 mx-auto">
|
||||||
<svg width="120" height="120">
|
<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="#e9ecef"
|
<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"
|
</svg>
|
||||||
r="52"
|
<div class="absolute inset-0 flex items-center justify-center">
|
||||||
cx="60"
|
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||||
cy="60"/>
|
<span class="text-3xl font-bold text-gray-900">{{ progress|floatformat:0 }}%</span>
|
||||||
<circle class="progress-ring-circle"
|
</div>
|
||||||
stroke="var(--kaauh-teal)"
|
</div>
|
||||||
stroke-width="8"
|
|
||||||
fill="transparent"
|
|
||||||
r="52"
|
|
||||||
cx="60"
|
|
||||||
cy="60"
|
|
||||||
style="stroke-dasharray: 326.73; stroke-dashoffset: {{ stroke_dashoffset }};"/>
|
|
||||||
</svg>
|
|
||||||
<div class="progress-ring-text">
|
|
||||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
|
||||||
{{ progress|floatformat:0 }}%
|
|
||||||
</div>
|
</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-gray-900 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 %}
|
|
||||||
<div class="progress-bar bg-primary-theme" style="width: {{ progress }}%"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-3 text-center">
|
|
||||||
{% if assignment.can_submit %}
|
|
||||||
<span class="badge bg-primary-theme">{% trans "Can Submit" %}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="badge bg-danger">{% trans "Cannot Submit" %}</span>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Assignment Info Card -->
|
|
||||||
<div class="col kaauh-card p-4">
|
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
|
||||||
<i class="fas fa-info me-2"></i>
|
|
||||||
{% trans "Assignment Info" %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="text-muted small">{% trans "Assigned Date" %}</label>
|
|
||||||
<div class="fw-bold">{{ assignment.assigned_date|date:"Y-m-d" }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="text-muted small">{% trans "Days Remaining" %}</label>
|
|
||||||
<div class="fw-bold {% if assignment.days_remaining <= 3 %}text-danger{% endif %}">
|
|
||||||
{{ assignment.days_remaining }} {% trans "days" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label class="text-muted small">{% trans "Submission Rate" %}</label>
|
|
||||||
<div class="fw-bold">
|
|
||||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||||
{{ progress|floatformat:1 }}%
|
<div class="w-full bg-gray-200 rounded-full h-2 mb-4">
|
||||||
</div>
|
<div class="h-2 rounded-full bg-temple-red" style="width: {{ progress }}%"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Recent Messages Section -->
|
<div class="text-center">
|
||||||
{% if message_page_obj %}
|
{% if assignment.can_submit %}
|
||||||
<div class="kaauh-card p-4 mt-4">
|
<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>
|
||||||
<h5 class="mb-4" style="color: var(--kaauh-teal-dark);">
|
{% else %}
|
||||||
<i class="fas fa-comments me-2"></i>
|
<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>
|
||||||
{% trans "Recent Messages" %}
|
|
||||||
</h5>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
{% for message in message_page_obj|slice:":6" %}
|
|
||||||
<div class="col-lg-6 mb-3">
|
|
||||||
<div class="message-item {% if not message.is_read %}unread{% endif %}">
|
|
||||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
||||||
<div class="fw-bold">{{ message.subject }}</div>
|
|
||||||
<small class="text-muted">{{ message.created_at|date:"Y-m-d H:i" }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="small text-muted mb-2">
|
|
||||||
{% trans "From" %}: {{ message.sender.get_full_name }}
|
|
||||||
</div>
|
|
||||||
<div class="small">{{ message.message|truncatewords:30 }}</div>
|
|
||||||
{% if not message.is_read %}
|
|
||||||
<span class="badge bg-info mt-2">{% trans "New" %}</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if message_page_obj.count > 6 %}
|
<!-- Assignment Info Card -->
|
||||||
<div class="text-center mt-3">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
<a href="#" class="btn btn-outline-primary btn-sm">
|
<div class="p-6">
|
||||||
{% trans "View All Messages" %}
|
<h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
</a>
|
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="info" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Assignment Info" %}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Assigned Date" %}</label>
|
||||||
|
<div class="font-bold text-gray-900">{{ assignment.assigned_date|date:"Y-m-d" }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Days Remaining" %}</label>
|
||||||
|
<div class="font-bold {% if assignment.days_remaining <= 3 %}text-red-600{% endif %} text-gray-900">
|
||||||
|
{{ assignment.days_remaining }} {% trans "days" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-semibold text-gray-500 mb-1">{% trans "Submission Rate" %}</label>
|
||||||
|
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||||
|
<div class="font-bold text-gray-900">{{ progress|floatformat:1 }}%</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Recent Messages Section -->
|
||||||
|
{% if message_page_obj %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||||||
|
<div class="p-6">
|
||||||
|
<h5 class="text-xl font-bold text-gray-900 mb-4 flex items-center gap-2">
|
||||||
|
<div class="w-10 h-10 bg-temple-red/10 rounded-lg flex items-center justify-center">
|
||||||
|
<i data-lucide="message-circle" class="w-5 h-5 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
{% trans "Recent Messages" %}
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
{% for message in message_page_obj|slice:":6" %}
|
||||||
|
<div class="border-l-4 border-temple-red bg-gray-50 rounded-r-lg p-4 {% if not message.is_read %}bg-blue-50 border-blue-500{% endif %}">
|
||||||
|
<div class="flex justify-between items-start mb-2">
|
||||||
|
<div class="font-semibold text-gray-900">{{ message.subject }}</div>
|
||||||
|
<div class="text-xs text-gray-500">{{ message.created_at|date:"Y-m-d H:i" }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-600 mb-2">
|
||||||
|
{% trans "From" %}: {{ message.sender.get_full_name }}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-700">{{ message.message|truncatewords:30 }}</div>
|
||||||
|
{% if not message.is_read %}
|
||||||
|
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800 mt-2">{% trans "New" %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if message_page_obj.count > 6 %}
|
||||||
|
<div class="text-center mt-4">
|
||||||
|
<a href="#" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl transition">
|
||||||
|
{% trans "View All Messages" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Message Modal -->
|
|
||||||
<div class="modal fade" id="messageModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
|
||||||
<i class="fas fa-envelope me-2"></i>
|
|
||||||
{% trans "Send Message to Admin" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<form method="post" action="#">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" name="assignment_id" value="{{ assignment.id }}">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="subject" class="form-label">
|
|
||||||
{% trans "Subject" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="subject" name="subject" required>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="priority" class="form-label">{% trans "Priority" %}</label>
|
|
||||||
<select class="form-select" id="priority" name="priority">
|
|
||||||
<option value="low">{% trans "Low" %}</option>
|
|
||||||
<option value="medium" selected>{% trans "Medium" %}</option>
|
|
||||||
<option value="high">{% trans "High" %}</option>
|
|
||||||
<option value="urgent">{% trans "Urgent" %}</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="content" class="form-label">
|
|
||||||
{% trans "Message" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="content" name="content" rows="5" required></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-outline-primary" data-bs-dismiss="modal">
|
|
||||||
{% trans "Cancel" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-paper-plane me-1"></i> {% trans "Send Message" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Edit application Modal -->
|
|
||||||
<div class="modal fade" id="editCandidateModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
|
||||||
<i class="fas fa-edit me-2"></i>
|
|
||||||
{% trans "Edit application" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<form id="editCandidateForm" method="post" action="{% url 'agency_portal_edit_application' 0 %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" id="edit_candidate_id" name="candidate_id">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_first_name" class="form-label">
|
|
||||||
{% trans "First Name" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="edit_first_name" name="first_name" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_last_name" class="form-label">
|
|
||||||
{% trans "Last Name" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="text" class="form-control" id="edit_last_name" name="last_name" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_email" class="form-label">
|
|
||||||
{% trans "Email" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="email" class="form-control" id="edit_email" name="email" required>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="edit_phone" class="form-label">
|
|
||||||
{% trans "Phone" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<input type="tel" class="form-control" id="edit_phone" name="phone" required>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="edit_address" class="form-label">
|
|
||||||
{% trans "Address" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
<textarea class="form-control" id="edit_address" name="address" rows="3" required></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
||||||
{% trans "Cancel" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Save Changes" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Delete Confirmation Modal -->
|
|
||||||
<div class="modal fade" id="deleteCandidateModal" tabindex="-1">
|
|
||||||
<div class="modal-dialog">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">
|
|
||||||
<i class="fas fa-trash me-2"></i>
|
|
||||||
{% trans "Remove application" %}
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
||||||
</div>
|
|
||||||
<form id="deleteCandidateForm" method="post" action="{% url 'agency_portal_delete_application' 0 %}">
|
|
||||||
{% csrf_token %}
|
|
||||||
<input type="hidden" id="delete_candidate_id" name="candidate_id">
|
|
||||||
<div class="modal-body">
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
||||||
{% trans "Are you sure you want to remove this application? This action cannot be undone." %}
|
|
||||||
</div>
|
|
||||||
<p><strong>{% trans "Application:" %}</strong> <span id="delete_candidate_name"></span></p>
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
||||||
{% trans "Cancel" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-danger">
|
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Remove Application" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
<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,230 +1,215 @@
|
|||||||
{% 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">
|
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
|
||||||
<i class="fas fa-tachometer-alt me-2"></i>
|
|
||||||
{% trans "Agency Dashboard" %}
|
|
||||||
</h1>
|
|
||||||
<p class="text-muted mb-0">
|
|
||||||
{% trans "Welcome back" %}, {{ agency.name }}!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{% comment %} <a href="{% url 'agency_portal_submit_application' %}" class="btn btn-main-action me-2">
|
|
||||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
>
|
<!-- Header Section -->
|
||||||
{% endif %}
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white rounded-2xl shadow-lg p-6 md:p-8">
|
||||||
</a> {% endcomment %}
|
<div class="flex flex-col md:flex-row md:justify-between md:items-start gap-4">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center gap-3 mb-2">
|
||||||
|
<div class="w-14 h-14 rounded-xl bg-white/20 backdrop-blur-sm flex items-center justify-center">
|
||||||
|
<i data-lucide="building" class="w-8 h-8"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-2xl md:text-3xl font-bold">{% trans "Agency Dashboard" %}</h1>
|
||||||
|
<p class="text-white/80">
|
||||||
|
{% trans "Welcome back" %}, {{ agency.name }}!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
|
||||||
<div class="kaauh-card shadow-sm h-100 px-2 py-2">
|
<!-- Active Assignments -->
|
||||||
<div class="card-body text-center">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="text-primary-theme mb-2">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<i class="fas fa-users fa-2x"></i>
|
<div class="flex items-center gap-3">
|
||||||
</div>
|
<i data-lucide="check-circle" class="w-6 h-6"></i>
|
||||||
<h4 class="card-title">{{ total_applications }}</h4>
|
<span class="text-sm font-semibold">{% trans "Active Assignments" %}</span>
|
||||||
<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>
|
||||||
<div class="col-md-3 mb-2">
|
|
||||||
<div class="kaauh-card shadow-sm h-100 px-2 py-2">
|
<!-- Total Applications -->
|
||||||
<div class="card-body text-center">
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<div class="text-warning mb-2">
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
<i class="fas fa-envelope fa-2x"></i>
|
<div class="flex items-center gap-3">
|
||||||
</div>
|
<i data-lucide="users" class="w-6 h-6"></i>
|
||||||
<h4 class="card-title">{{ total_unread_messages }}</h4>
|
<span class="text-sm font-semibold">{% trans "Total Applications" %}</span>
|
||||||
<p class="card-text text-muted">{% trans "Unread Messages" %}</p>
|
|
||||||
</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">
|
<!-- Assignment Header -->
|
||||||
<div class="card-body">
|
<div class="bg-temple-cream border-b border-gray-200 p-4">
|
||||||
<!-- Assignment Header -->
|
<div class="flex justify-between items-start gap-2 mb-2">
|
||||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
<div class="flex-1">
|
||||||
<div class="flex-grow-1">
|
<h5 class="font-bold text-temple-dark mb-1">
|
||||||
<h6 class="card-title 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" }}
|
||||||
{% if stats.days_remaining >= 0 %}
|
<div class="text-xs text-gray-500 font-normal">
|
||||||
({{ stats.days_remaining }} {% trans "days left" %})
|
{% if stats.days_remaining >= 0 %}
|
||||||
{% else %}
|
({{ stats.days_remaining }} {% trans "days left" %})
|
||||||
({{ stats.days_remaining }} {% trans "days overdue" %})
|
{% else %}
|
||||||
{% endif %}
|
({{ stats.days_remaining }} {% trans "days overdue" %})
|
||||||
</strong>
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div>
|
||||||
<small class="text-muted d-block">{% trans "Applications" %}</small>
|
<div class="text-xs text-gray-500 mb-1">{% trans "Applications" %}</div>
|
||||||
<strong>{{ stats.application_count }} / {{ stats.assignment.max_candidates}}</strong>
|
<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>
|
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
|
||||||
<div>
|
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">
|
||||||
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}"
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
class="btn btn-sm btn-main-action">
|
{% trans "View" %}
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
</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>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</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,335 +1,245 @@
|
|||||||
{% 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">
|
||||||
<!-- Login Header -->
|
<div class="bg-white rounded-2xl shadow-2xl border-none max-w-2xl w-full">
|
||||||
<div class="login-header">
|
|
||||||
<div class="mb-3">
|
<!-- Login Header -->
|
||||||
<i class="fas fa-building fa-3x"></i>
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-8 rounded-t-2xl text-center">
|
||||||
|
<div class="mb-4 inline-flex items-center justify-center w-20 h-20 rounded-full bg-white/20 backdrop-blur-sm">
|
||||||
|
<i data-lucide="building" class="w-10 h-10"></i>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-2xl md:text-3xl font-bold mb-2">{% trans "Agency Portal" %}</h2>
|
||||||
|
<p class="text-white/80 text-sm md:text-base">
|
||||||
|
{% trans "Submit candidates for job assignments" %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="mb-2">{% trans "Agency Portal" %}</h3>
|
|
||||||
<p class="mb-0 opacity-75">
|
|
||||||
{% trans "Submit candidates for job assignments" %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Login Body -->
|
<!-- Login Body -->
|
||||||
<div class="login-body">
|
<div class="p-8 md:p-10">
|
||||||
<!-- Messages -->
|
|
||||||
|
|
||||||
|
<!-- Login Form -->
|
||||||
|
<form method="post" novalidate id="login-form" class="space-y-6">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
<!-- Login Form -->
|
<!-- Access Token Field -->
|
||||||
<form method="post" novalidate>
|
<div class="space-y-2">
|
||||||
{% csrf_token %}
|
<label for="{{ form.token.id_for_label }}" class="block text-sm font-bold text-gray-700">
|
||||||
|
<i data-lucide="key" class="w-4 h-4 inline mr-2"></i>
|
||||||
<!-- Access Token Field -->
|
{% trans "Access Token" %}
|
||||||
<div class="mb-3">
|
</label>
|
||||||
<label for="{{ form.token.id_for_label }}" class="form-label fw-bold">
|
<div class="relative">
|
||||||
<i class="fas fa-key me-2"></i>
|
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-temple-red">
|
||||||
{% trans "Access Token" %}
|
<i data-lucide="lock" class="w-5 h-5"></i>
|
||||||
</label>
|
</span>
|
||||||
<div class="input-group">
|
<input type="text"
|
||||||
<span class="input-group-text">
|
name="token"
|
||||||
<i class="fas fa-lock"></i>
|
id="{{ form.token.id_for_label }}"
|
||||||
</span>
|
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"
|
||||||
{{ form.token }}
|
placeholder="{% trans 'Enter your access token' %}"
|
||||||
</div>
|
required>
|
||||||
{% if form.token.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{% for error in form.token.errors %}{{ error }}{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% if form.token.errors %}
|
||||||
<small class="form-text text-muted">
|
<div class="text-red-600 text-sm mt-1 flex items-center gap-1">
|
||||||
{% trans "Enter the access token provided by the hiring organization" %}
|
<i data-lucide="alert-circle" class="w-4 h-4"></i>
|
||||||
</small>
|
{% for error in form.token.errors %}{{ error }}{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<!-- Password Field -->
|
<p class="text-xs text-gray-500">
|
||||||
<div class="mb-4">
|
{% trans "Enter the access token provided by the hiring organization" %}
|
||||||
<label for="{{ form.password.id_for_label }}" class="form-label fw-bold">
|
</p>
|
||||||
<i class="fas fa-shield-alt me-2"></i>
|
|
||||||
{% trans "Password" %}
|
|
||||||
</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-text">
|
|
||||||
<i class="fas fa-key"></i>
|
|
||||||
</span>
|
|
||||||
{{ form.password }}
|
|
||||||
</div>
|
</div>
|
||||||
{% if form.password.errors %}
|
|
||||||
<div class="text-danger small mt-1">
|
|
||||||
{% for error in form.password.errors %}{{ error }}{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<small class="form-text text-muted">
|
|
||||||
{% trans "Enter the password for this access token" %}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Submit Button -->
|
<!-- Password Field -->
|
||||||
<div class="d-grid">
|
<div class="space-y-2">
|
||||||
<button type="submit" class="btn btn-login btn-lg">
|
<label for="{{ form.password.id_for_label }}" class="block text-sm font-bold text-gray-700">
|
||||||
<i class="fas fa-sign-in-alt me-2"></i>
|
<i data-lucide="shield-check" class="w-4 h-4 inline mr-2"></i>
|
||||||
|
{% trans "Password" %}
|
||||||
|
</label>
|
||||||
|
<div class="relative">
|
||||||
|
<span class="absolute left-4 top-1/2 -translate-y-1/2 text-temple-red">
|
||||||
|
<i data-lucide="key" class="w-5 h-5"></i>
|
||||||
|
</span>
|
||||||
|
<input type="password"
|
||||||
|
name="password"
|
||||||
|
id="{{ form.password.id_for_label }}"
|
||||||
|
class="w-full pl-12 pr-12 py-3.5 border border-gray-300 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition"
|
||||||
|
placeholder="{% trans 'Enter your password' %}"
|
||||||
|
required>
|
||||||
|
<button type="button"
|
||||||
|
onclick="togglePassword()"
|
||||||
|
id="password-toggle"
|
||||||
|
class="absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 transition">
|
||||||
|
<i data-lucide="eye" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% if form.password.errors %}
|
||||||
|
<div class="text-red-600 text-sm mt-1 flex items-center gap-1">
|
||||||
|
<i data-lucide="alert-circle" class="w-4 h-4"></i>
|
||||||
|
{% for error in form.password.errors %}{{ error }}{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
{% trans "Enter the password for this access token" %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Submit Button -->
|
||||||
|
<button type="submit"
|
||||||
|
class="w-full bg-gradient-to-r from-temple-red to-[#7a1a29] hover:from-[#8a1d2d] hover:to-[#6a1520] text-white font-bold py-4 rounded-xl text-sm transition shadow-lg hover:shadow-xl transform hover:-translate-y-0.5 flex items-center justify-center gap-2">
|
||||||
|
<i data-lucide="log-in" class="w-5 h-5"></i>
|
||||||
{% trans "Access Portal" %}
|
{% 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>
|
||||||
|
<h6 class="font-bold text-gray-900 mb-1">{% trans "Contact Support" %}</h6>
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
{% trans "Reach out to your hiring contact" %}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h6 class="fw-bold">{% trans "Contact Support" %}</h6>
|
<div>
|
||||||
<small class="text-muted">
|
<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">
|
||||||
{% trans "Reach out to your hiring contact" %}
|
<i data-lucide="book-open" class="w-8 h-8"></i>
|
||||||
</small>
|
</div>
|
||||||
</div>
|
<h6 class="font-bold text-gray-900 mb-1">{% trans "Documentation" %}</h6>
|
||||||
<div class="col-6 mb-3">
|
<p class="text-sm text-gray-500">
|
||||||
<div class="feature-icon mx-auto">
|
{% trans "View user guides and tutorials" %}
|
||||||
<i class="fas fa-question-circle"></i>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h6 class="fw-bold">{% trans "Documentation" %}</h6>
|
|
||||||
<small class="text-muted">
|
|
||||||
{% trans "View user guides and tutorials" %}
|
|
||||||
</small>
|
|
||||||
</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();
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Show/hide password functionality
|
// Focus on load
|
||||||
|
accessTokenInput.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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';
|
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
|
||||||
|
passwordField.setAttribute('type', type);
|
||||||
|
|
||||||
passwordToggle.addEventListener('click', function() {
|
const icon = type === 'password' ? 'eye' : 'eye-off';
|
||||||
const type = passwordField.getAttribute('type') === 'password' ? 'text' : 'password';
|
toggleBtn.innerHTML = `<i data-lucide="${icon}" class="w-5 h-5"></i>`;
|
||||||
passwordField.setAttribute('type', type);
|
lucide.createIcons();
|
||||||
this.innerHTML = type === 'password' ? '<i class="fas fa-eye"></i>' : '<i class="fas fa-eye-slash"></i>';
|
|
||||||
});
|
|
||||||
|
|
||||||
passwordField.parentElement.appendChild(passwordToggle);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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();
|
||||||
showError('{% trans "Please enter your access token." %}');
|
showError('{% trans "Please enter your access token." %}');
|
||||||
accessTokenInput.focus();
|
accessTokenInput.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!password) {
|
if (!password) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showError('{% trans "Please enter your password." %}');
|
showError('{% trans "Please enter your password." %}');
|
||||||
passwordField.focus();
|
passwordField.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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">
|
</button>
|
||||||
<i class="fas fa-plus me-1"></i> {% trans "Add New Applicant" %}
|
|
||||||
</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>
|
|
||||||
<h4 class="card-title">{{ total_persons }}</h4>
|
|
||||||
<p class="card-text text-muted">{% trans "Total Persons" %}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h4 class="text-2xl font-bold text-gray-900 mb-1">{{ total_persons }}</h4>
|
||||||
|
<p class="text-gray-600">{% trans "Total Persons" %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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="check-circle" class="w-6 h-6 text-temple-red"></i>
|
||||||
<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>
|
||||||
|
<h4 class="text-2xl font-bold text-gray-900 mb-1">{{ page_obj|length }}</h4>
|
||||||
|
<p class="text-gray-600">{% trans "Showing on this page" %}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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 class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Name" %}</th>
|
||||||
<th scope="col">{% trans "Name" %}</th>
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Email" %}</th>
|
||||||
<th scope="col">{% trans "Email" %}</th>
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Phone" %}</th>
|
||||||
<th scope="col">{% trans "Phone" %}</th>
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Created At" %}</th>
|
||||||
<th scope="col">{% trans "Created At" %}</th>
|
<th class="text-center py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Actions" %}</th>
|
||||||
{% comment %} <th scope="col">{% trans "Stage" %}</th>
|
</tr>
|
||||||
<th scope="col">{% trans "Applied Date" %}</th> {% endcomment %}
|
</thead>
|
||||||
<th scope="col" class="text-center">{% trans "Actions" %}</th>
|
<tbody>
|
||||||
</tr>
|
{% for person in page_obj %}
|
||||||
</thead>
|
<tr class="border-b border-gray-100 hover:bg-gray-50 transition cursor-pointer">
|
||||||
<tbody>
|
<td class="py-4 px-4">
|
||||||
{% for person in page_obj %}
|
<div class="flex items-center gap-3">
|
||||||
<tr class="person-row">
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center font-bold text-sm">
|
||||||
<td>
|
{{ person.first_name|first|upper }}{{ person.last_name|first|upper }}
|
||||||
<div class="d-flex align-items-center">
|
</div>
|
||||||
<div class="rounded-circle bg-primary-theme text-white d-flex align-items-center justify-content-center me-2"
|
<div>
|
||||||
style="width: 32px; height: 32px; font-size: 14px; font-weight: 600;">
|
<div class="font-semibold text-gray-900">{{ person.first_name }} {{ person.last_name }}</div>
|
||||||
{{ person.first_name|first|upper }}{{ person.last_name|first|upper }}
|
{% if person.address %}
|
||||||
</div>
|
<div class="text-sm text-gray-500">{{ person.address|truncatechars:50 }}</div>
|
||||||
<div>
|
{% endif %}
|
||||||
<div class="fw-semibold">{{ person.first_name }} {{ person.last_name }}</div>
|
</div>
|
||||||
{% if person.address %}
|
</div>
|
||||||
<small class="text-muted">{{ person.address|truncatechars:50 }}</small>
|
</td>
|
||||||
{% endif %}
|
<td class="py-4 px-4">
|
||||||
</div>
|
<a href="mailto:{{ person.email }}" class="text-gray-900 hover:text-temple-red transition">{{ person.email }}</a>
|
||||||
</div>
|
</td>
|
||||||
</td>
|
<td class="py-4 px-4">{{ person.phone|default:"-" }}</td>
|
||||||
<td>
|
<td class="py-4 px-4">{{ person.created_at|date:"d-m-Y" }}</td>
|
||||||
<a href="mailto:{{ person.email }}" class="text-decoration-none text-dark">
|
<td class="py-4 px-4 text-center">
|
||||||
{{ person.email }}
|
<button type="button" data-bs-toggle="modal" data-bs-target="#updateModal"
|
||||||
</a>
|
hx-get="{% url 'person_update' person.slug %}"
|
||||||
</td>
|
hx-target="#updateModalBody"
|
||||||
<td>{{ person.phone|default:"-" }}</td>
|
hx-swap="outerrHTML"
|
||||||
{% comment %} <td>
|
hx-select="#person-form"
|
||||||
<span class="badge bg-light text-dark">
|
hx-vals='{"view":"portal"}'
|
||||||
{{ person.job.title|truncatechars:30 }}
|
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"
|
||||||
</span>
|
title="{% trans 'Edit Person' %}">
|
||||||
</td>
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
<td>
|
</button>
|
||||||
{% with stage_class=person.stage|lower %}
|
</td>
|
||||||
<span class="stage-badge
|
</tr>
|
||||||
{% if stage_class == 'applied' %}bg-secondary{% endif %}
|
{% endfor %}
|
||||||
{% if stage_class == 'exam' %}bg-info{% endif %}
|
</tbody>
|
||||||
{% if stage_class == 'interview' %}bg-warning{% endif %}
|
</table>
|
||||||
{% if stage_class == 'offer' %}bg-success{% endif %}
|
|
||||||
{% if stage_class == 'hired' %}bg-primary{% endif %}
|
|
||||||
{% if stage_class == 'rejected' %}bg-danger{% endif %}
|
|
||||||
text-white">
|
|
||||||
{{ person.get_stage_display }}
|
|
||||||
</span>
|
|
||||||
{% endwith %}
|
|
||||||
</td> {% endcomment %}
|
|
||||||
<td>{{ person.created_at|date:"d-m-Y" }}</td>
|
|
||||||
<td class="text-center">
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<button type="button" data-bs-toggle="modal" data-bs-target="#updateModal"
|
|
||||||
hx-get="{% url 'person_update' person.slug %}"
|
|
||||||
hx-target="#updateModalBody"
|
|
||||||
hx-swap="outerrHTML"
|
|
||||||
hx-select="#person-form"
|
|
||||||
hx-vals='{"view":"portal"}'
|
|
||||||
class="btn btn-sm btn-outline-secondary"
|
|
||||||
title="{% trans 'Edit Person' %}"
|
|
||||||
>
|
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% 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>
|
<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>
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
||||||
<li class="page-item">
|
</a>
|
||||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
|
||||||
<i class="fas fa-angle-left"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% 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>
|
<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>
|
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
||||||
<li class="page-item">
|
</a>
|
||||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}">
|
|
||||||
<i class="fas fa-angle-double-right"></i>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% 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="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="row g-4">
|
<div>
|
||||||
<div class="col-md-6">
|
{{ person_form.email|as_crispy_field }}
|
||||||
{{ person_form.email|as_crispy_field }}
|
{{person_form.errors}}
|
||||||
{{person_form.errors}}
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ person_form.phone|as_crispy_field }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{{ person_form.phone|as_crispy_field }}
|
<div>
|
||||||
|
{{ person_form.gpa|as_crispy_field }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ person_form.national_id|as_crispy_field }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div class="row g-4">
|
<div>
|
||||||
<div class="col-md-6">
|
{{ person_form.date_of_birth|as_crispy_field }}
|
||||||
{{ person_form.gpa|as_crispy_field }}
|
</div>
|
||||||
|
<div>
|
||||||
|
{{ person_form.nationality|as_crispy_field }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div>
|
||||||
{{ person_form.national_id|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-md-6">
|
|
||||||
{{ person_form.date_of_birth|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
{{ person_form.nationality|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-12">
|
|
||||||
{{ 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-red-100 text-lg">{% trans "Enter details to create a new application record." %}</p>
|
||||||
<p class="text-white opacity-75 mb-0">{% trans "Enter details to create a new application record." %}</p>
|
</div>
|
||||||
</div>
|
<div class="flex gap-2">
|
||||||
<div class="d-flex gap-2 mt-1">
|
<button type="button" class="modal-trigger bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2" data-modal="personModal">
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#personModal">
|
<i data-lucide="user-plus" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-user-plus me-1"></i>
|
<span class="hidden sm:inline">{% trans "Create New Applicant" %}</span>
|
||||||
<span class="d-none d-sm-inline">{% trans "Create New Applicant" %}</span>
|
</button>
|
||||||
</button>
|
<a href="{% url 'application_list' %}" class="bg-white/20 hover:bg-white/30 backdrop-blur-sm text-white px-4 py-2 rounded-lg text-sm font-medium transition flex items-center gap-2" title="{% trans 'Back to List' %}">
|
||||||
<a href="{% url 'application_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-arrow-left"></i>
|
<span class="hidden sm:inline">{% trans "Back to List" %}</span>
|
||||||
<span class="d-none d-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>
|
||||||
|
<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 class="col-md-4">
|
|
||||||
{{ person_form.last_name|as_crispy_field }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
<div class="row g-4">
|
<div>
|
||||||
<div class="col-md-6">
|
<label for="{{ person_form.email.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
{{ person_form.email|as_crispy_field }}
|
{{ person_form.email.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.email }}
|
||||||
|
{% for error in person_form.email.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="{{ person_form.phone.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.phone.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.phone }}
|
||||||
|
{% for error in person_form.phone.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
{{ person_form.phone|as_crispy_field }}
|
<div>
|
||||||
|
<label for="{{ person_form.gpa.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.gpa.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.gpa }}
|
||||||
|
{% for error in person_form.gpa.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="{{ person_form.national_id.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.national_id.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.national_id }}
|
||||||
|
{% for error in person_form.national_id.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
||||||
<div class="row g-4">
|
<div>
|
||||||
<div class="col-md-6">
|
<label for="{{ person_form.date_of_birth.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
{{ person_form.gpa|as_crispy_field }}
|
{{ person_form.date_of_birth.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.date_of_birth }}
|
||||||
|
{% for error in person_form.date_of_birth.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="{{ person_form.nationality.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.nationality.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.nationality }}
|
||||||
|
{% for error in person_form.nationality.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="mt-4">
|
||||||
{{ person_form.national_id|as_crispy_field }}
|
<label for="{{ person_form.address.id_for_label }}" class="block text-sm font-semibold text-gray-700 mb-2">
|
||||||
|
{{ person_form.address.label }}
|
||||||
|
</label>
|
||||||
|
{{ person_form.address }}
|
||||||
|
{% for error in person_form.address.errors %}
|
||||||
|
<p class="text-sm text-red-500 mt-1">{{ error }}</p>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-md-6">
|
|
||||||
{{ person_form.date_of_birth|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
|
||||||
{{ person_form.nationality|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row g-4">
|
|
||||||
<div class="col-12">
|
|
||||||
{{ person_form.address|as_crispy_field }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</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>
|
||||||
</label>
|
</span>
|
||||||
</div>
|
</label>
|
||||||
</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>
|
</button>
|
||||||
{% trans "Contact & Job" %}
|
</li>
|
||||||
</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 class="role-presentation">
|
||||||
onclick="showTab('timeline-pane')"
|
<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">
|
||||||
data-tab="timeline-pane">
|
<i data-lucide="route" class="w-4 h-4"></i> {% trans "Journey Timeline" %}
|
||||||
<i data-lucide="route" class="w-4 h-4 mr-2 inline"></i>
|
</button>
|
||||||
{% trans "Journey Timeline" %}
|
</li>
|
||||||
</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 class="role-presentation">
|
||||||
onclick="showTab('documents-pane')"
|
<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">
|
||||||
data-tab="documents-pane">
|
<i data-lucide="file-text" class="w-4 h-4"></i> {% trans "Documents" %}
|
||||||
<i data-lucide="file-text" class="w-4 h-4 mr-2 inline"></i>
|
</button>
|
||||||
{% trans "Documents" %}
|
</li>
|
||||||
</button>
|
</ul>
|
||||||
{% if application.parsed_summary %}
|
|
||||||
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
|
|
||||||
onclick="showTab('summary-pane')"
|
|
||||||
data-tab="summary-pane">
|
|
||||||
<i data-lucide="sparkles" class="w-4 h-4 mr-2 inline"></i>
|
|
||||||
{% trans "AI Summary" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
{% if application.is_resume_parsed %}
|
|
||||||
<button class="tab-btn px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 border-transparent hover:bg-gray-100 transition"
|
|
||||||
onclick="showTab('analysis-pane')"
|
|
||||||
data-tab="analysis-pane">
|
|
||||||
<i data-lucide="bar-chart-3" class="w-4 h-4 mr-2 inline"></i>
|
|
||||||
{% trans "AI Analysis" %}
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</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>
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
{% trans "Core Details" %}
|
<div class="flex items-center gap-3 p-4 bg-gray-50 rounded-lg">
|
||||||
</h5>
|
<i data-lucide="mail" class="w-8 h-8 text-gray-400 shrink-0"></i>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
||||||
<div class="info-card bg-gray-50 rounded-xl p-4 border border-gray-200">
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
||||||
<i data-lucide="mail" class="w-6 h-6 text-temple-red"></i>
|
|
||||||
</div>
|
|
||||||
<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 items-center gap-3">
|
<div class="flex-1">
|
||||||
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
<p class="text-xs text-gray-500">{% trans "Applied Date" %}</p>
|
||||||
<i data-lucide="calendar-check" class="w-6 h-6 text-temple-red"></i>
|
<div class="flex items-center gap-3">
|
||||||
</div>
|
<p class="font-semibold text-gray-800">{{ application.created_at|date:"M d, Y H:i" }}</p>
|
||||||
<div>
|
<span class="bg-gray-200 text-gray-700 px-2 py-1 rounded text-xs font-medium">
|
||||||
<p class="text-xs text-gray-500 uppercase tracking-wide mb-1">{% trans "Applied Date" %}</p>
|
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i>
|
||||||
<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,372 +103,395 @@
|
|||||||
</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 class="p-6">
|
|
||||||
|
|
||||||
<p class="text-xs font-bold text-gray-500 uppercase tracking-wide mb-4">{% trans "Current Stage" %}</p>
|
|
||||||
<div class="bg-temple-red/5 border border-temple-red/30 rounded-xl p-4 mb-6">
|
|
||||||
<p class="text-xl font-bold text-temple-red mb-1">{{ application.stage }}</p>
|
|
||||||
<p class="text-xs text-gray-500">
|
|
||||||
{% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
<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">{% trans "Current Stage" %}</h6>
|
||||||
<div class="relative pl-8 border-l-2 border-gray-200 space-y-6">
|
<div class="p-4 mb-4 rounded-lg bg-red-50 border border-red-200">
|
||||||
|
<p class="font-bold text-lg text-temple-dark mb-1">{{ application.stage }}</p>
|
||||||
{# Application Submitted #}
|
<p class="text-xs text-gray-500">
|
||||||
<div class="timeline-item relative">
|
{% trans "Latest status update:" %} {{ application.updated_at|date:"M d, Y" }}
|
||||||
<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">
|
</p>
|
||||||
<i data-lucide="file-signature" class="w-3 h-3"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Application Submitted" %}</p>
|
|
||||||
<p class="text-xs text-gray-500">
|
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
|
||||||
{{ application.created_at|date:"M d, Y" }}
|
|
||||||
<span class="mx-2">|</span>
|
|
||||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
|
||||||
{{ application.created_at|date:"h:i A" }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if application.exam_date %}
|
<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="timeline-item relative">
|
<div class="relative pl-8">
|
||||||
<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-3.5 top-0 bottom-0 w-0.5 bg-gray-200"></div>
|
||||||
<i data-lucide="clipboard-check" class="w-3 h-3"></i>
|
|
||||||
</div>
|
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Exam" %}</p>
|
|
||||||
<p class="text-xs text-gray-500">
|
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
|
||||||
{{ application.exam_date|date:"M d, Y" }}
|
|
||||||
<span class="mx-2">|</span>
|
|
||||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
|
||||||
{{ application.exam_date|date:"h:i A" }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if application.get_interview_date %}
|
<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-amber-500 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="message-circle" class="w-3 h-3"></i>
|
</div>
|
||||||
|
<div class="ml-12">
|
||||||
|
<p class="font-semibold text-gray-800">{% trans "Application Submitted" %}</p>
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.created_at|date:"M d, Y" }}
|
||||||
|
<span class="mx-2">|</span>
|
||||||
|
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.created_at|date:"h:i A" }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Interview" %}</p>
|
|
||||||
<p class="text-xs text-gray-500">
|
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
|
||||||
{{ application.get_interview_date }}
|
|
||||||
<span class="mx-2">|</span>
|
|
||||||
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
|
||||||
{{ application.get_interview_time }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if application.offer_date %}
|
{% 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-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="clipboard-check" class="w-4 h-4"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-12">
|
||||||
|
<p class="font-semibold text-gray-800">{% trans "Exam" %}</p>
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.exam_date|date:"M d, Y" }}
|
||||||
|
<span class="mx-2">|</span>
|
||||||
|
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.exam_date|date:"h:i A" }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
{% endif %}
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Offer" %}</p>
|
|
||||||
<p class="text-xs text-gray-500">
|
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
|
||||||
{{ application.offer_date|date:"M d, Y" }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if application.hired_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-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="message-circle" class="w-4 h-4"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-12">
|
||||||
|
<p class="font-semibold text-gray-800">{% trans "Interview" %}</p>
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.get_interview_date}}
|
||||||
|
<span class="mx-2">|</span>
|
||||||
|
<i data-lucide="clock" class="inline w-3 h-3 mr-1"></i> {{ application.get_interview_time}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ml-4">
|
{% endif %}
|
||||||
<p class="text-sm font-semibold text-gray-900 mb-1">{% trans "Hired" %}</p>
|
|
||||||
<p class="text-xs text-gray-500">
|
{% if application.offer_date %}
|
||||||
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
<div class="relative mb-6">
|
||||||
{{ application.hired_date|date:"M d, Y" }}
|
<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">
|
||||||
</p>
|
<i data-lucide="handshake" class="w-4 h-4"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-12">
|
||||||
|
<p class="font-semibold text-gray-800">{% trans "Offer" %}</p>
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.offer_date|date:"M d, Y" }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if application.hired_date %}
|
||||||
|
<div class="relative mb-6">
|
||||||
|
<div class="absolute left-0 top-0 w-8 h-8 bg-temple-red rounded-full flex items-center justify-center text-white z-10 border-4 border-white">
|
||||||
|
<i data-lucide="check-circle" class="w-4 h-4"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-12">
|
||||||
|
<p class="font-semibold text-gray-800">{% trans "Hired" %}</p>
|
||||||
|
<p class="text-xs text-gray-500">
|
||||||
|
<i data-lucide="calendar" class="inline w-3 h-3 mr-1"></i> {{ application.hired_date|date:"M d, Y" }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{# TAB 3: DOCUMENTS #}
|
|
||||||
<div id="documents-pane" class="tab-content hidden">
|
|
||||||
{% with documents=application.documents %}
|
|
||||||
{% include 'includes/document_list.html' %}
|
|
||||||
{% endwith %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# TAB 4: AI SUMMARY #}
|
|
||||||
{% if application.parsed_summary %}
|
|
||||||
<div id="summary-pane" class="tab-content hidden">
|
|
||||||
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
|
|
||||||
<i data-lucide="sparkles" class="w-5 h-5"></i>
|
|
||||||
{% trans "AI Generated Summary" %}
|
|
||||||
</h5>
|
|
||||||
<div class="bg-gray-50 rounded-xl p-4 border-l-4 border-temple-red">
|
|
||||||
{% include 'includes/application_modal_body.html' %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# TAB 5: AI ANALYSIS #}
|
|
||||||
{% if application.is_resume_parsed %}
|
|
||||||
<div id="analysis-pane" class="tab-content hidden">
|
|
||||||
<h5 class="text-lg font-bold text-temple-red mb-4 flex items-center gap-2">
|
|
||||||
<i data-lucide="bar-chart-3" class="w-5 h-5"></i>
|
|
||||||
{% trans "AI Analysis Report" %}
|
|
||||||
</h5>
|
|
||||||
<div class="bg-gray-50 rounded-xl p-4 border-l-4 border-temple-red">
|
|
||||||
{% with analysis=application.ai_analysis_data %}
|
|
||||||
|
|
||||||
{# Match Score Card #}
|
|
||||||
<div class="bg-white rounded-xl p-4 mb-4 shadow-sm">
|
|
||||||
<div class="flex justify-between items-center mb-3">
|
|
||||||
<h6 class="font-bold text-gray-900">{% trans "Match Score" %}</h6>
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-bold {% if analysis.match_score >= 70 %}bg-emerald-500 text-white{% elif analysis.match_score >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
|
|
||||||
{{ analysis.match_score }}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="h-2 bg-gray-200 rounded-full overflow-hidden">
|
|
||||||
<div class="progress-bar h-full {% if analysis.match_score >= 70 %}bg-emerald-500{% elif analysis.match_score >= 40 %}bg-amber-500{% else %}bg-red-500{% endif %}"
|
|
||||||
style="width: {{ analysis.match_score }}%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Category & Job Fit #}
|
|
||||||
<div class="mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Category" %}</h6>
|
|
||||||
<p class="text-gray-700 mb-3">{{ analysis.category }}</p>
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Job Fit Narrative" %}</h6>
|
|
||||||
<p class="text-gray-700">{{ analysis.job_fit_narrative }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Strengths and Weaknesses #}
|
|
||||||
<div class="mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Strengths" %}</h6>
|
|
||||||
<p class="text-emerald-600 mb-3">{{ analysis.strengths }}</p>
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Weaknesses" %}</h6>
|
|
||||||
<p class="text-red-600">{{ analysis.weaknesses }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Recommendation #}
|
|
||||||
<div class="bg-temple-red/5 rounded-xl p-4 mb-4 border border-temple-red/20">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Recommendation" %}</h6>
|
|
||||||
<p class="text-gray-700">{{ analysis.recommendation }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Top Keywords #}
|
|
||||||
<div class="mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Top Keywords" %}</h6>
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
{% for keyword in analysis.top_3_keywords %}
|
|
||||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-temple-red/10 text-temple-red">
|
|
||||||
{{ keyword }}
|
|
||||||
</span>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Professional Details #}
|
|
||||||
<div class="bg-white rounded-xl p-4 shadow-sm mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-3">{% trans "Professional Details" %}</h6>
|
|
||||||
<div class="space-y-2">
|
|
||||||
<p class="flex justify-between"><span class="text-gray-600">{% trans "Years of Experience:" %}</span> <strong>{{ analysis.years_of_experience }}</strong></p>
|
|
||||||
<p class="flex justify-between"><span class="text-gray-600">{% trans "Most Recent Job Title:" %}</span> <strong>{{ analysis.most_recent_job_title }}</strong></p>
|
|
||||||
<p class="flex justify-between">
|
|
||||||
<span class="text-gray-600">{% trans "Experience Industry Match:" %}</span>
|
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-bold {% if analysis.experience_industry_match >= 70 %}bg-emerald-500 text-white{% elif analysis.experience_industry_match >= 40 %}bg-amber-500 text-white{% else %}bg-red-500 text-white{% endif %}">
|
|
||||||
{{ analysis.experience_industry_match }}%
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
<p class="flex justify-between"><span class="text-gray-600">{% trans "Soft Skills Score:" %}</span> <strong>{{ analysis.soft_skills_score }}%</strong></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Criteria Checklist #}
|
|
||||||
<div class="mb-4">
|
|
||||||
<h6 class="font-bold text-temple-red mb-2">{% trans "Criteria Assessment" %}</h6>
|
|
||||||
<div class="overflow-x-auto">
|
|
||||||
<table class="w-full text-sm">
|
|
||||||
<thead>
|
|
||||||
<tr class="border-b border-gray-200">
|
|
||||||
<th class="text-left py-2 px-3 font-semibold text-gray-700">{% trans "Criteria" %}</th>
|
|
||||||
<th class="text-left py-2 px-3 font-semibold text-gray-700">{% trans "Status" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for criterion, status in analysis.criteria_checklist.items %}
|
|
||||||
<tr class="border-b border-gray-100">
|
|
||||||
<td class="py-2 px-3 text-gray-700">{{ criterion }}</td>
|
|
||||||
<td class="py-2 px-3">
|
|
||||||
{% if status == "Met" %}
|
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-emerald-100 text-emerald-700">{% trans "Met" %}</span>
|
|
||||||
{% elif status == "Not Met" %}
|
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-red-100 text-red-700">{% trans "Not Met" %}</span>
|
|
||||||
{% else %}
|
|
||||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold bg-gray-100 text-gray-700">{{ status }}</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
{# TAB 3 CONTENT: DOCUMENTS #}
|
||||||
|
<div class="tab-pane hidden" id="documents-pane" role="tabpanel" aria-labelledby="documents-tab">
|
||||||
|
{% with documents=application.documents %}
|
||||||
|
{% include 'includes/document_list.html' %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# RIGHT COLUMN: ACTIONS #}
|
{# RIGHT COLUMN: ACTIONS AND TIMING #}
|
||||||
<div class="lg:col-span-4 space-y-4">
|
<div class="space-y-6">
|
||||||
|
{# ACTIONS CARD #}
|
||||||
{# Management Actions #}
|
<div class="bg-white rounded-xl shadow-md p-6 border border-gray-200">
|
||||||
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
<h5 class="text-sm font-semibold text-gray-600 mb-4 flex items-center gap-2">
|
||||||
<h5 class="text-sm font-bold text-gray-600 mb-4 flex items-center gap-2">
|
<i data-lucide="settings" class="w-4 h-4"></i>{% trans "Management Actions" %}
|
||||||
<i data-lucide="settings" class="w-5 h-5"></i>
|
|
||||||
{% trans "Management Actions" %}
|
|
||||||
</h5>
|
</h5>
|
||||||
<div class="space-y-2">
|
<div class="space-y-3">
|
||||||
<a href="{% url 'application_list' %}"
|
<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">
|
||||||
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>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</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>
|
||||||
{% with days=application.time_to_hire_days %}
|
<div class="text-center">
|
||||||
<p class="text-2xl font-bold text-temple-red">
|
{% with days=application.time_to_hire_days %}
|
||||||
{% if days > 0 %}{{ days }} day{{ days|pluralize }}{% else %}0{% endif %}
|
{% if days > 0 %}
|
||||||
</p>
|
<p class="text-2xl font-bold text-temple-dark">{{ days }}</p>
|
||||||
{% endwith %}
|
<p class="text-sm text-gray-500">day{{ days|pluralize }}</p>
|
||||||
</div>
|
|
||||||
|
|
||||||
{# Resume Parsing Section #}
|
|
||||||
<div class="detail-card bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
|
||||||
<h5 class="text-sm font-bold text-gray-600 mb-3 flex items-center gap-2">
|
|
||||||
<i data-lucide="file-text" class="w-5 h-5"></i>
|
|
||||||
{% trans "Resume Analysis" %}
|
|
||||||
</h5>
|
|
||||||
<div class="resume-parsed-section">
|
|
||||||
{% if application.is_resume_parsed %}
|
|
||||||
{% include 'recruitment/application_resume_template.html' %}
|
|
||||||
{% else %}
|
|
||||||
{% if application.scoring_timeout %}
|
|
||||||
<div class="flex flex-col items-center justify-center py-6">
|
|
||||||
<div class="flex items-center gap-2 text-temple-red">
|
|
||||||
<i data-lucide="bot" class="w-6 h-6 animate-pulse"></i>
|
|
||||||
<span class="text-sm font-medium">{% trans "Resume Analysis In Progress..." %}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="flex flex-col items-center justify-center py-6">
|
<p class="text-2xl font-bold text-temple-dark">0</p>
|
||||||
<button type="submit"
|
<p class="text-sm text-gray-500">days</p>
|
||||||
class="btn-primary inline-flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-white font-medium px-4 py-2.5 rounded-xl text-sm transition shadow-lg hover:shadow-xl"
|
|
||||||
hx-get="{% url 'application_retry_scoring' application.slug %}"
|
|
||||||
hx-select=".resume-parsed-section"
|
|
||||||
hx-target=".resume-parsed-section"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-on:click="this.disabled=true;this.innerHTML=`Scoring Resume, Please Wait...`">
|
|
||||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
|
||||||
{% trans "Unable to Parse Resume, Click to Retry" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Stage Update Modal for Staff Users #}
|
<div class="resume-parsed-section">
|
||||||
{% if user.is_staff %}
|
{% if application.is_resume_parsed %}
|
||||||
<div id="stageUpdateModal" class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
|
{% include 'recruitment/application_resume_template.html' %}
|
||||||
<div class="bg-white rounded-2xl shadow-xl max-w-lg w-full">
|
{% else %}
|
||||||
{% include "recruitment/partials/stage_update_modal.html" with application=application form=stage_form %}
|
{% if application.scoring_timeout %}
|
||||||
|
<div class="flex justify-center items-center py-12">
|
||||||
|
<div class="flex items-center gap-3 text-temple-red">
|
||||||
|
<i data-lucide="robot" class="w-8 h-8 animate-pulse"></i>
|
||||||
|
<span class="text-sm">{% trans "Resume Analysis In Progress..." %}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% else %}
|
||||||
|
<div class="flex justify-center items-center py-12">
|
||||||
|
<button type="submit" class="flex items-center gap-2 bg-temple-red hover:bg-red-800 text-white py-3 px-6 rounded-lg text-sm font-medium transition shadow-sm" hx-get="{% url 'application_retry_scoring' application.slug %}" hx-select=".resume-parsed-section" hx-target=".resume-parsed-section" hx-swap="outerHTML" hx-on:click="this.disabled=true;this.innerHTML=`Scoring Resume, Please Wait... <i data-lucide='loader-2' class='w-4 h-4 animate-spin'></i>`">
|
||||||
|
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||||
|
{% trans "Unable to Parse Resume, click to retry" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# STAGE UPDATE MODAL INCLUDED FOR STAFF USERS #}
|
||||||
|
{% if user.is_staff %}
|
||||||
|
{% include "recruitment/partials/stage_update_modal.html" with application=application form=stage_form %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block customJS %}
|
||||||
<script>
|
<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');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add active state to clicked button
|
||||||
|
this.classList.add('active', 'text-temple-dark', 'border-b-temple-red', 'bg-white');
|
||||||
|
this.classList.remove('text-gray-600', 'border-transparent');
|
||||||
|
this.setAttribute('aria-selected', 'true');
|
||||||
|
|
||||||
|
// Hide all tab panes
|
||||||
|
tabPanes.forEach(pane => {
|
||||||
|
pane.classList.add('hidden');
|
||||||
|
pane.classList.remove('block');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show the target tab pane
|
||||||
|
const targetPane = document.getElementById(targetTabId);
|
||||||
|
if (targetPane) {
|
||||||
|
targetPane.classList.remove('hidden');
|
||||||
|
targetPane.classList.add('block');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Remove active state from all tabs
|
// ========================================
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
// Document Upload Modal Functionality
|
||||||
btn.classList.remove('active');
|
// ========================================
|
||||||
btn.classList.remove('border-temple-red', 'text-temple-red', 'bg-white');
|
|
||||||
btn.classList.add('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);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Show selected tab content
|
// Close modal buttons (both close X and Cancel button)
|
||||||
document.getElementById(tabId).classList.remove('hidden');
|
document.querySelectorAll('.modal-close-btn, .modal-cancel-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (uploadModal && isModalOpen) {
|
||||||
|
uploadModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Add active state to selected tab
|
// Close modal when clicking outside
|
||||||
const activeBtn = document.querySelector(`[data-tab="${tabId}"]`);
|
if (uploadModal) {
|
||||||
activeBtn.classList.add('active', 'border-temple-red', 'text-temple-red', 'bg-white');
|
uploadModal.addEventListener('click', function(e) {
|
||||||
activeBtn.classList.remove('border-transparent', 'text-gray-600');
|
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');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Stage Update Modal Functionality
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
const stageModal = document.getElementById('stageUpdateModal');
|
||||||
|
let isStageModalOpen = false;
|
||||||
|
const currentStage = '{{ application.stage }}';
|
||||||
|
|
||||||
|
// Open stage modal buttons
|
||||||
|
const openStageModalBtns = document.querySelectorAll('.stage-modal-trigger');
|
||||||
|
openStageModalBtns.forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (stageModal) {
|
||||||
|
stageModal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
isStageModalOpen = true;
|
||||||
|
// Initialize Lucide icons inside modal
|
||||||
|
setTimeout(() => lucide.createIcons(), 100);
|
||||||
|
// Set initial stage selection
|
||||||
|
const stageSelect = document.getElementById('id_stage');
|
||||||
|
if (stageSelect) {
|
||||||
|
stageSelect.value = currentStage;
|
||||||
|
// Call updateStageInfo if function exists
|
||||||
|
if (typeof updateStageInfo === 'function') {
|
||||||
|
updateStageInfo(currentStage, currentStage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stage selection change handler
|
||||||
|
const stageSelect = document.getElementById('id_stage');
|
||||||
|
if (stageSelect) {
|
||||||
|
stageSelect.addEventListener('change', function(e) {
|
||||||
|
const selectedValue = e.target.value;
|
||||||
|
if (typeof updateStageInfo === 'function') {
|
||||||
|
updateStageInfo(selectedValue, currentStage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stage modal buttons
|
||||||
|
document.querySelectorAll('#stageUpdateModal button').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (stageModal && isStageModalOpen) {
|
||||||
|
stageModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isStageModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close stage modal when clicking outside
|
||||||
|
if (stageModal) {
|
||||||
|
stageModal.addEventListener('click', function(e) {
|
||||||
|
if (e.target === stageModal || (e.target.classList.contains('fixed') && e.target.classList.contains('inset-0'))) {
|
||||||
|
stageModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isStageModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stage modal on escape key
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && stageModal && isStageModalOpen) {
|
||||||
|
stageModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isStageModalOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close modal after successful HTMX form submission
|
||||||
|
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
||||||
|
// Close document upload modal
|
||||||
|
if (evt.detail.successful && uploadModal && isModalOpen) {
|
||||||
|
const form = evt.detail.elt;
|
||||||
|
if (form && form.tagName === 'FORM' && form.action.includes('document_upload')) {
|
||||||
|
uploadModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isModalOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close stage update modal
|
||||||
|
if (evt.detail.successful && stageModal && isStageModalOpen) {
|
||||||
|
const form = evt.detail.elt;
|
||||||
|
if (form && form.tagName === 'FORM' && form.action.includes('application_update_stage')) {
|
||||||
|
stageModal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
isStageModalOpen = false;
|
||||||
|
// Reload page to update stage display
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Reinitialize Lucide icons after HTMX updates
|
||||||
|
// ========================================
|
||||||
|
document.body.addEventListener('htmx:afterSwap', function(evt) {
|
||||||
|
lucide.createIcons();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -3,267 +3,113 @@
|
|||||||
|
|
||||||
{% block title %}{% trans "Update" %} {{ object.name }} - {{ block.super }}{% endblock %}
|
{% block 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" class="mb-6">
|
||||||
<nav aria-label="breadcrumb">
|
<ol class="flex items-center space-x-2 text-sm">
|
||||||
<ol class="breadcrumb">
|
<li>
|
||||||
<li class="breadcrumb-item">
|
<a href="{% url 'application_list' %}" class="text-gray-500 hover:text-temple-red transition">
|
||||||
<a href="{% url 'application_list' %}" class="text-decoration-none text-secondary">
|
<i data-lucide="users" class="w-4 h-4 inline mr-1"></i> {% trans "Applications" %}
|
||||||
<i class="fas fa-users me-1"></i> {% trans "Applications" %}
|
</a>
|
||||||
</a>
|
</li>
|
||||||
</li>
|
<li class="text-gray-400">/</li>
|
||||||
<li class="breadcrumb-item">
|
<li>
|
||||||
<a href="{% url 'application_detail' object.slug %}" class="text-decoration-none text-secondary">
|
<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="
|
</ol>
|
||||||
color: #F43B5E; /* Rosy Accent Color */
|
</nav>
|
||||||
font-weight: 600;">{% trans "Update" %}</li>
|
|
||||||
</ol>
|
|
||||||
</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>
|
||||||
</h3>
|
{% trans "Update Application" %}
|
||||||
<div class="d-flex gap-2">
|
</h3>
|
||||||
<a href="{% url 'application_detail' object.slug %}" class="btn btn-outline-secondary">
|
<div class="flex gap-2">
|
||||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
<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">
|
||||||
</a>
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
<a href="{% url 'application_delete' object.slug %}" class="btn btn-danger">
|
{% trans "View Details" %}
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Delete" %}
|
</a>
|
||||||
</a>
|
<a href="{% url 'application_delete' object.slug %}" class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
<a href="{% url 'application_list' %}" class="btn btn-outline-secondary">
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to List" %}
|
{% trans "Delete" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<a href="{% url 'application_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
{% trans "Back to List" %}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
</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>
|
||||||
{% if object.profile_image %}
|
{% trans "Currently Editing" %}
|
||||||
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}"
|
</h6>
|
||||||
class="current-image">
|
<div class="flex items-center gap-4">
|
||||||
{% else %}
|
{% if object.profile_image %}
|
||||||
<div class="current-image d-flex align-items-center justify-content-center bg-light">
|
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}"
|
||||||
<i class="fas fa-user text-muted"></i>
|
class="w-24 h-24 rounded-full border-2 border-temple-red object-cover">
|
||||||
</div>
|
{% else %}
|
||||||
{% endif %}
|
<div class="w-24 h-24 rounded-full border-2 border-gray-300 bg-gray-100 flex items-center justify-center">
|
||||||
<div>
|
<i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
|
||||||
<h5 class="mb-1">{{ object.name }}</h5>
|
|
||||||
{% if object.email %}
|
|
||||||
<p class="text-muted mb-0">{{ object.email }}</p>
|
|
||||||
{% endif %}
|
|
||||||
<small class="text-muted">
|
|
||||||
{% trans "Created" %}: {{ object.created_at|date:"d M Y" }} •
|
|
||||||
{% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }}
|
|
||||||
</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
<h5 class="text-lg font-semibold text-gray-900 mb-1">{{ object.name }}</h5>
|
||||||
|
{% if object.email %}
|
||||||
|
<p class="text-gray-600 text-sm mb-1">{{ object.email }}</p>
|
||||||
|
{% endif %}
|
||||||
|
<p class="text-gray-500 text-xs">
|
||||||
|
{% trans "Created" %}: {{ object.created_at|date:"d M Y" }} •
|
||||||
|
{% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
||||||
</h5>
|
{% trans "Error" %}
|
||||||
{% for error in form.non_field_errors %}
|
</h5>
|
||||||
<p class="mb-0">{{ error }}</p>
|
{% for error in form.non_field_errors %}
|
||||||
{% endfor %}
|
<p class="text-red-700 text-sm">{{ error }}</p>
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if messages %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
|
||||||
{{ message }}
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="{% trans 'Close' %}"></button>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form method="post" action="{% url 'application_update' object.slug %}" enctype="multipart/form-data" id="candidate-form">
|
{% if messages %}
|
||||||
{% csrf_token %}
|
{% for message in messages %}
|
||||||
{{form|crispy}}
|
<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">
|
||||||
</form>
|
{{ message }}
|
||||||
<div class="d-flex gap-2">
|
|
||||||
<button form="candidate-form" type="submit" class="btn btn-main-action">
|
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Update" %}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'application_update' object.slug %}" enctype="multipart/form-data" id="candidate-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form|crispy }}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="flex gap-2 mt-6">
|
||||||
|
<button form="candidate-form" type="submit" class="bg-temple-red hover:bg-red-800 text-white font-semibold px-8 py-3 rounded-xl transition shadow-md hover:shadow-lg flex items-center gap-2">
|
||||||
|
<i data-lucide="save" class="w-5 h-5"></i>
|
||||||
|
{% trans "Update" %}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -273,6 +119,26 @@
|
|||||||
{% 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,358 +1,214 @@
|
|||||||
{% 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 -->
|
||||||
{% trans "Application List" %}
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-4 gap-3">
|
||||||
<span class="badge bg-primary-theme ms-2">{{ applications|length }} / {{ total_candidates }} Total</span>
|
<h2 class="text-xl font-bold text-temple-dark flex items-center gap-2">
|
||||||
<small class="text-muted fw-normal ms-2">({% trans "Sorted by AI Score" %})</small>
|
<i data-lucide="users" class="w-5 h-5"></i>
|
||||||
</h2>
|
{% trans "Application List" %}
|
||||||
|
<span class="ml-2 px-2 py-1 bg-temple-red text-white text-xs rounded-full">
|
||||||
|
{{ applications|length }} / {{ total_candidates }} {% trans "Total" %}
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
<div class="text-xs text-gray-500">
|
||||||
|
{% trans "Sorted by AI Score" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="kaauh-card shadow-sm p-3">
|
<!-- Main Card -->
|
||||||
{% if applications %}
|
<div class="bg-white border border-gray-200 rounded-xl shadow-sm overflow-hidden">
|
||||||
<div class="bulk-action-bar p-3 bg-light border-bottom">
|
<!-- Bulk Action Bar -->
|
||||||
<form hx-boost="true" hx-include="#application-form" action="{% url 'application_update_status' job.slug %}" method="post" class="action-group">
|
{% if applications %}
|
||||||
{% csrf_token %}
|
<div class="p-3 sm:p-4 bg-gray-50 border-b border-gray-200">
|
||||||
|
<form hx-boost="true" hx-include="#application-form"
|
||||||
|
action="{% url 'application_update_status' job.slug %}"
|
||||||
|
method="post"
|
||||||
|
class="flex flex-col sm:flex-row gap-3 sm:gap-4 items-end">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
{# Using d-flex for horizontal alignment and align-items-end to align items to the bottom baseline #}
|
<div class="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"
|
</button>
|
||||||
hx-include="#application-form"
|
</form>
|
||||||
title="Email Participants">
|
</div>
|
||||||
<i class="fas fa-envelope"></i>
|
{% endif %}
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
<!-- Table -->
|
||||||
</form>
|
<div class="overflow-x-auto">
|
||||||
</div>
|
<form id="application-form" action="{% url 'application_update_status' job.slug %}" method="post">
|
||||||
{% endif %}
|
|
||||||
<div class="table-responsive">
|
|
||||||
<form id="application-form" 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">
|
||||||
</td>
|
{{ application.match_score|default:"0" }}%
|
||||||
<td>
|
</span>
|
||||||
{{application.exam_date|date:"d-m-Y h:i A"|default:"--"}}
|
</td>
|
||||||
</td>
|
<td class="px-4 py-3 border-b border-gray-200 text-sm">
|
||||||
<td id="exam-score-{{ application.pk}}">
|
{{application.exam_date|date:"d-m-Y h:i A"|default:"--"}}
|
||||||
{{application.exam_score|default:"--"}}
|
</td>
|
||||||
</td>
|
<td class="px-4 py-3 border-b border-gray-200 text-sm" id="exam-score-{{ application.pk }}">
|
||||||
|
{{application.exam_score|default:"--"}}
|
||||||
<td class="text-center" id="status-result-{{ application.pk}}">
|
</td>
|
||||||
{% if not application.exam_status %}
|
<td class="px-4 py-3 text-center border-b border-gray-200" id="status-result-{{ application.pk }}">
|
||||||
<button type="button" class="btn btn-warning btn-sm"
|
{% if not application.exam_status %}
|
||||||
data-bs-toggle="modal"
|
<button type="button"
|
||||||
data-bs-target="#candidateviewModal"
|
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"
|
||||||
hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}"
|
onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}')"
|
||||||
hx-target="#candidateviewModalBody"
|
title="{% trans 'Pass Exam' %}">
|
||||||
title="Pass Exam">
|
<i data-lucide="plus" class="w-3 h-3"></i>
|
||||||
<i class="fas fa-plus"></i>
|
</button>
|
||||||
|
{% else %}
|
||||||
|
{% if application.exam_status %}
|
||||||
|
<button type="button"
|
||||||
|
class="px-3 py-1.5 {% if application.exam_status == 'Passed' %}bg-green-500 hover:bg-green-600 text-white{% else %}bg-red-500 hover:bg-red-600 text-white{% endif %} rounded-lg transition text-xs font-medium"
|
||||||
|
onclick="openCandidateModal('{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}')"
|
||||||
|
title="{% trans 'Update Exam Status' %}">
|
||||||
|
{{ application.exam_status }}
|
||||||
</button>
|
</button>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if application.exam_status %}
|
|
||||||
<button type="button" class="btn btn-{% if application.exam_status == 'Passed' %}success{% else %}danger{% endif %} btn-sm"
|
|
||||||
data-bs-toggle="modal"
|
|
||||||
data-bs-target="#candidateviewModal"
|
|
||||||
hx-get="{% url 'update_application_status' job.slug application.slug 'exam' 'passed' %}"
|
|
||||||
hx-target="#candidateviewModalBody"
|
|
||||||
title="Pass Exam">
|
|
||||||
{{ application.exam_status }}
|
|
||||||
</button>
|
|
||||||
{% else %}
|
|
||||||
--
|
--
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
{% endif %}
|
||||||
<td><button type="button" class="btn btn-outline-primary btn-sm"
|
</td>
|
||||||
data-bs-toggle="modal"
|
<td class="px-4 py-3 border-b border-gray-200">
|
||||||
data-bs-target="#noteModal"
|
<button type="button"
|
||||||
hx-get="{% url 'application_add_note' application.slug %}"
|
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-swap="innerHTML"
|
onclick="openNoteModal('{% url 'application_add_note' application.slug %}')">
|
||||||
hx-target=".notemodal">
|
<i data-lucide="plus-circle" class="w-3 h-3"></i>
|
||||||
<i class="fas fa-calendar-plus me-1"></i>
|
{% trans "Add note" %}
|
||||||
Add note
|
</button>
|
||||||
</button></td>
|
</td>
|
||||||
|
<td class="px-4 py-3 border-b border-gray-200 text-center">
|
||||||
<td >
|
<button type="button"
|
||||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
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-toggle="modal"
|
onclick="openCandidateModal('{% url 'application_criteria_view_htmx' application.pk %}')"
|
||||||
data-bs-target="#candidateviewModal"
|
title="{% trans 'View Application Profile' %}">
|
||||||
hx-get="{% url 'application_criteria_view_htmx' application.pk %}"
|
<i data-lucide="eye" class="w-3 h-3"></i>
|
||||||
hx-target="#candidateviewModalBody"
|
</button>
|
||||||
title="View Profile">
|
</td>
|
||||||
<i class="fas fa-eye"></i>
|
</tr>
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</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>
|
||||||
|
|
||||||
|
<!-- Note Modal -->
|
||||||
|
<div id="noteModal" class="fixed inset-0 z-50 hidden" aria-labelledby="noteModalLabel" role="dialog" aria-modal="true">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeNoteModal()"></div>
|
||||||
|
|
||||||
|
<!-- Modal Content -->
|
||||||
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-4xl max-h-[90vh] overflow-hidden flex flex-col">
|
||||||
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-dark" id="noteModalLabel">
|
||||||
|
<i data-lucide="sticky-note" class="w-5 h-5 inline mr-2"></i>{% trans "Add Note" %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeNoteModal()" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="noteModalBody" class="flex-1 overflow-y-auto p-6">
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading note form..." %}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stage Confirmation Modal -->
|
||||||
|
<div id="stageConfirmationModal" class="fixed inset-0 z-50 hidden" aria-labelledby="stageConfirmationModalLabel" role="dialog" aria-modal="true">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" onclick="closeStageConfirmationModal()"></div>
|
||||||
|
|
||||||
|
<!-- Modal Content -->
|
||||||
|
<div class="relative z-10 flex min-h-screen items-center justify-center p-4">
|
||||||
|
<div class="bg-white border border-gray-200 rounded-xl shadow-lg w-full max-w-lg">
|
||||||
|
<div class="flex justify-between items-center px-6 py-4 border-b border-gray-200">
|
||||||
|
<h5 class="text-lg font-bold text-temple-dark" id="stageConfirmationModalLabel">
|
||||||
|
<i data-lucide="info" class="w-5 h-5 inline mr-2"></i>{% trans "Confirm Stage Change" %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" onclick="closeStageConfirmationModal()" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="flex items-center justify-center py-3 mb-3">
|
||||||
|
<i data-lucide="arrow-right-left" class="w-16 h-16 text-temple-red"></i>
|
||||||
|
</div>
|
||||||
|
<p class="text-center mb-2 text-base text-gray-800">
|
||||||
|
<span id="stageConfirmationMessage">{% trans "Are you sure you want to change to this stage?" %}</span>
|
||||||
|
</p>
|
||||||
|
<div class="bg-blue-50 border border-blue-200 text-blue-800 px-4 py-3 rounded-lg text-center" role="alert">
|
||||||
|
<i data-lucide="user-check" class="w-4 h-4 inline mr-2"></i>
|
||||||
|
<span class="font-semibold">{% trans "Selected Stage:" %}</span>
|
||||||
|
<span id="targetStageName" class="font-bold">{% trans "--" %}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 py-4 border-t border-gray-200 flex justify-end gap-2">
|
||||||
|
<button type="button" class="px-4 py-2 border-2 border-gray-300 text-gray-600 rounded-lg hover:border-temple-red hover:text-temple-red transition text-sm font-medium" onclick="closeStageConfirmationModal()">
|
||||||
|
<i data-lucide="x" class="w-4 h-4 inline mr-1"></i>{% trans "Cancel" %}
|
||||||
|
</button>
|
||||||
|
<button type="button" class="px-4 py-2 bg-temple-red text-white rounded-lg hover:bg-temple-red/90 transition text-sm font-medium" id="confirmStageChangeButton">
|
||||||
|
<i data-lucide="check" class="w-4 h-4 inline mr-1"></i>{% trans "Confirm" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% include "recruitment/partials/note_modal.html" %}
|
|
||||||
{% include "recruitment/partials/stage_confirmation_modal.html" %}
|
|
||||||
|
|
||||||
{% 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') {
|
||||||
|
lucide.createIcons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Modal Functions
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
function openCandidateModal(url) {
|
||||||
|
const modal = document.getElementById('candidateviewModal');
|
||||||
|
const modalBody = document.getElementById('candidateviewModalBody');
|
||||||
|
|
||||||
|
// Reset content
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading application data..." %}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Load content via HTMX
|
||||||
|
if (url && typeof htmx !== 'undefined') {
|
||||||
|
htmx.ajax('GET', url, {target: '#candidateviewModalBody', swap: 'innerHTML'});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinitialize icons
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeCandidateModal() {
|
||||||
|
const modal = document.getElementById('candidateviewModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEmailModal() {
|
||||||
|
const modal = document.getElementById('emailModal');
|
||||||
|
const modalBody = document.getElementById('emailModalBody');
|
||||||
|
const applicationForm = document.getElementById('application-form');
|
||||||
|
const url = '{% url "compose_application_email" job.slug %}';
|
||||||
|
|
||||||
|
// Reset content
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading email form..." %}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Load content via HTMX with form data
|
||||||
|
if (url && typeof htmx !== 'undefined' && applicationForm) {
|
||||||
|
htmx.ajax('GET', url, {
|
||||||
|
target: '#emailModalBody',
|
||||||
|
swap: 'innerHTML',
|
||||||
|
values: htmx.values(applicationForm)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinitialize icons
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEmailModal() {
|
||||||
|
const modal = document.getElementById('emailModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openNoteModal(url) {
|
||||||
|
const modal = document.getElementById('noteModal');
|
||||||
|
const modalBody = document.getElementById('noteModalBody');
|
||||||
|
|
||||||
|
// Reset content
|
||||||
|
modalBody.innerHTML = `
|
||||||
|
<div class="text-center py-10 text-gray-500">
|
||||||
|
<i data-lucide="loader-2" class="w-10 h-10 animate-spin mx-auto mb-3 text-temple-red"></i>
|
||||||
|
<p>{% trans "Loading note form..." %}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
// Load content via HTMX
|
||||||
|
if (url && typeof htmx !== 'undefined') {
|
||||||
|
htmx.ajax('GET', url, {target: '#noteModalBody', swap: 'innerHTML'});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reinitialize icons
|
||||||
|
setTimeout(() => {
|
||||||
|
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeNoteModal() {
|
||||||
|
const modal = document.getElementById('noteModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openStageConfirmationModal(selectedStage) {
|
||||||
|
const modal = document.getElementById('stageConfirmationModal');
|
||||||
|
const messageElement = document.getElementById('stageConfirmationMessage');
|
||||||
|
const targetStageElement = document.getElementById('targetStageName');
|
||||||
|
|
||||||
|
// Update confirmation message
|
||||||
|
if (messageElement && targetStageElement) {
|
||||||
|
const checkedCount = Array.from(document.querySelectorAll('.rowCheckbox:checked')).length;
|
||||||
|
if (checkedCount > 0) {
|
||||||
|
messageElement.textContent = `{% trans "Are you sure you want to move" %} ${checkedCount} {% trans "candidate(s) to this stage?" %}`;
|
||||||
|
targetStageElement.textContent = selectedStage;
|
||||||
|
} else {
|
||||||
|
messageElement.textContent = '{% trans "Please select at least one candidate." %}';
|
||||||
|
targetStageElement.textContent = '--';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeStageConfirmationModal() {
|
||||||
|
const modal = document.getElementById('stageConfirmationModal');
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// Checkbox and Form Logic
|
||||||
|
// ========================================
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
||||||
const rowCheckboxes = document.querySelectorAll('.rowCheckbox');
|
const 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 %}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -3,236 +3,72 @@
|
|||||||
|
|
||||||
{% block title %}{% trans "Delete Applicant" %} - {{ block.super }}{% endblock %}
|
{% block 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>
|
||||||
</label>
|
</span>
|
||||||
</div>
|
</label>
|
||||||
</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,71 +85,84 @@
|
|||||||
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">
|
||||||
</div>
|
<i data-lucide="calendar" class="w-8 h-8 text-temple-red"></i>
|
||||||
<div class="text-center">
|
</div>
|
||||||
<p class="text-muted small mb-0">Tenhal | تنحل</p>
|
{% trans "Interview Calendar" %}
|
||||||
|
</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 class="p-6" id="interview-info">
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" id="interview-info">
|
|
||||||
</div>
|
|
||||||
</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>
|
</div>
|
||||||
<td><strong>{% trans "Phone:" %}</strong></td>
|
<div class="flex">
|
||||||
<td>{{ interview.candidate.phone|default:"Not provided" }}</td>
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Email:" %}</span>
|
||||||
</tr>
|
<span class="text-gray-900">{{ interview.candidate.email }}</span>
|
||||||
</table>
|
</div>
|
||||||
|
<div class="flex">
|
||||||
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Phone:" %}</span>
|
||||||
|
<span class="text-gray-900">{{ interview.candidate.phone|default:"{% trans 'Not provided' %}" }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
|
||||||
<h5>{% trans "Interview Details" %}</h5>
|
<!-- Interview Details -->
|
||||||
<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 "Date:" %}</strong></td>
|
<i data-lucide="clock" class="w-5 h-5"></i>
|
||||||
<td>{{ interview.interview_date|date:"l, F j, Y" }}</td>
|
{% trans "Interview Details" %}
|
||||||
</tr>
|
</h3>
|
||||||
<tr>
|
<div class="space-y-3">
|
||||||
<td><strong>{% trans "Time:" %}</strong></td>
|
<div class="flex">
|
||||||
<td>{{ interview.interview_time|time:"g:i A" }}</td>
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Date:" %}</span>
|
||||||
</tr>
|
<span class="text-gray-900">{{ interview.interview_date|date:"l, F j, Y" }}</span>
|
||||||
<tr>
|
</div>
|
||||||
<td><strong>{% trans "Status:" %}</strong></td>
|
<div class="flex">
|
||||||
<td>
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Time:" %}</span>
|
||||||
<span class="status-badge status-{{ interview.status }}">
|
<span class="text-gray-900">{{ interview.interview_time|time:"g:i A" }}</span>
|
||||||
{{ interview.status|title }}
|
</div>
|
||||||
</span>
|
<div class="flex items-center">
|
||||||
</td>
|
<span class="font-semibold text-gray-700 w-28 flex-shrink-0">{% trans "Status:" %}</span>
|
||||||
</tr>
|
<span class="status-badge inline-flex items-center px-3 py-1 rounded-full text-sm font-medium
|
||||||
</table>
|
{% if interview.status == 'scheduled' %}bg-blue-100 text-blue-800
|
||||||
|
{% elif interview.status == 'confirmed' %}bg-green-100 text-green-800
|
||||||
|
{% elif interview.status == 'cancelled' %}bg-red-100 text-red-800
|
||||||
|
{% else %}bg-gray-100 text-gray-800{% endif %}">
|
||||||
|
{{ interview.status|title }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
<h4 class="mb-3">{{ title }}</h4>
|
|
||||||
<p class="text-muted mb-4">{{ message }}</p>
|
|
||||||
|
|
||||||
{% if unread_count > 0 %}
|
|
||||||
<div class="alert alert-info mb-4">
|
|
||||||
<h6 class="alert-heading">
|
|
||||||
<i class="fas fa-info-circle me-2"></i>{% trans "What this will do" %}
|
|
||||||
</h6>
|
|
||||||
<p class="mb-2">
|
|
||||||
{% blocktrans count count=unread_count %}
|
|
||||||
This will mark {{ count }} unread notification as read.
|
|
||||||
{% plural %}
|
|
||||||
This will mark all {{ count }} unread notifications as read.
|
|
||||||
{% endblocktrans %}
|
|
||||||
</p>
|
|
||||||
<p class="mb-0">
|
|
||||||
{% trans "You can still view all notifications in your notification list, but they won't appear as unread." %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="alert alert-success mb-4">
|
|
||||||
<h6 class="alert-heading">
|
|
||||||
<i class="fas fa-check-circle me-2"></i>{% trans "All caught up!" %}
|
|
||||||
</h6>
|
|
||||||
<p class="mb-0">
|
|
||||||
{% trans "You don't have any unread notifications to mark as read." %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if unread_count > 0 %}
|
|
||||||
<form method="post" class="d-inline">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-success">
|
|
||||||
<i class="fas fa-check-double me-1"></i> {% trans "Yes, Mark All as Read" %}
|
|
||||||
</button>
|
|
||||||
<a href="{{ cancel_url }}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
|
||||||
</a>
|
|
||||||
</form>
|
|
||||||
{% else %}
|
|
||||||
<a href="{{ cancel_url }}" class="btn btn-primary">
|
|
||||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Notifications" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h4 class="text-2xl font-bold text-gray-900 mb-3">{{ title }}</h4>
|
||||||
|
<p class="text-gray-600 mb-6">{{ message }}</p>
|
||||||
|
|
||||||
|
{% if unread_count > 0 %}
|
||||||
|
<div class="bg-blue-50 border border-blue-200 rounded-xl p-5 mb-6 text-left">
|
||||||
|
<h6 class="text-lg font-bold text-blue-800 flex items-center gap-2 mb-3">
|
||||||
|
<i data-lucide="info" class="w-5 h-5"></i>
|
||||||
|
{% trans "What this will do" %}
|
||||||
|
</h6>
|
||||||
|
<p class="text-blue-700 mb-3">
|
||||||
|
{% blocktrans count count=unread_count %}
|
||||||
|
This will mark {{ count }} unread notification as read.
|
||||||
|
{% plural %}
|
||||||
|
This will mark all {{ count }} unread notifications as read.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<p class="text-blue-700">
|
||||||
|
{% trans "You can still view all notifications in your notification list, but they won't appear as unread." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="bg-green-50 border border-green-200 rounded-xl p-5 mb-6 text-left">
|
||||||
|
<h6 class="text-lg font-bold text-green-800 flex items-center gap-2 mb-2">
|
||||||
|
<i data-lucide="check-circle" class="w-5 h-5"></i>
|
||||||
|
{% trans "All caught up!" %}
|
||||||
|
</h6>
|
||||||
|
<p class="text-green-700">
|
||||||
|
{% trans "You don't have any unread notifications to mark as read." %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if unread_count > 0 %}
|
||||||
|
<form method="post" class="inline-flex gap-3">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="inline-flex items-center gap-2 bg-green-500 hover:bg-green-600 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="check-circle-2" class="w-4 h-4"></i>
|
||||||
|
{% trans "Yes, Mark All as Read" %}
|
||||||
|
</button>
|
||||||
|
<a href="{{ cancel_url }}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-6 py-2.5 rounded-xl transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ cancel_url }}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
||||||
|
{% trans "Back to Notifications" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|
||||||
<h4 class="mb-3">{{ title }}</h4>
|
|
||||||
<p class="text-muted mb-4">{{ message }}</p>
|
|
||||||
|
|
||||||
<div class="alert alert-light mb-4">
|
|
||||||
<h6 class="alert-heading">{% trans "Notification Preview" %}</h6>
|
|
||||||
<p class="mb-2"><strong>{% trans "Message:" %}</strong> {{ notification.message|truncatewords:20 }}</p>
|
|
||||||
<p class="mb-0">
|
|
||||||
<strong>{% trans "Created:" %}</strong> {{ notification.created_at|date:"Y-m-d H:i" }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form method="post" class="d-inline">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-danger">
|
|
||||||
<i class="fas fa-trash me-1"></i> {% trans "Yes, Delete" %}
|
|
||||||
</button>
|
|
||||||
<a href="{{ cancel_url }}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
|
||||||
</a>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h4 class="text-2xl font-bold text-gray-900 mb-3">{{ title }}</h4>
|
||||||
|
<p class="text-gray-600 mb-6">{{ message }}</p>
|
||||||
|
|
||||||
|
<div class="bg-gray-50 border border-gray-200 rounded-xl p-5 mb-6 text-left">
|
||||||
|
<h6 class="text-lg font-bold text-gray-800 mb-3">{% trans "Notification Preview" %}</h6>
|
||||||
|
<p class="mb-2"><strong class="text-gray-700">{% trans "Message:" %}</strong> {{ notification.message|truncatewords:20 }}</p>
|
||||||
|
<p>
|
||||||
|
<strong class="text-gray-700">{% trans "Created:" %}</strong> {{ notification.created_at|date:"Y-m-d H:i" }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post" class="inline-flex gap-3">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
|
{% trans "Yes, Delete" %}
|
||||||
|
</button>
|
||||||
|
<a href="{{ cancel_url }}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-6 py-2.5 rounded-xl transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
{% trans "Cancel" %}
|
||||||
|
</a>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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,118 +81,111 @@
|
|||||||
</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>
|
||||||
<div class="col-md-4">
|
<div class="bg-white rounded-xl shadow-md border border-yellow-500 p-6 text-center">
|
||||||
<div class="card border-warning">
|
<h3 class="text-3xl font-bold text-yellow-600 mb-1">{{ unread_notifications }}</h3>
|
||||||
<div class="card-body text-center">
|
<p class="text-gray-600">{% trans "Unread" %}</p>
|
||||||
<h5 class="card-title text-warning">{{ unread_notifications }}</h5>
|
|
||||||
<p class="card-text">{% trans "Unread" %}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="bg-white rounded-xl shadow-md border border-blue-500 p-6 text-center">
|
||||||
<div class="card border-info">
|
<h3 class="text-3xl font-bold text-blue-600 mb-1">{{ email_notifications }}</h3>
|
||||||
<div class="card-body text-center">
|
<p class="text-gray-600">{% trans "Email Notifications" %}</p>
|
||||||
<h5 class="card-title text-info">{{ email_notifications }}</h5>
|
|
||||||
<p class="card-text">{% trans "Email Notifications" %}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</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="p-4 hover:bg-gray-50 transition {% if notification.status == 'PENDING' %}bg-blue-50{% endif %}">
|
||||||
<div class="list-group-item list-group-item-action {% if notification.status == 'PENDING' %}bg-light{% endif %}">
|
<div class="flex justify-between items-start gap-4">
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="flex-1 min-w-0">
|
||||||
<div class="flex-grow-1">
|
<div class="flex items-center flex-wrap gap-2 mb-2">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
||||||
<span class="badge bg-{{ notification.get_status_bootstrap_class }} me-2">
|
{% if notification.status == 'PENDING' %}bg-yellow-100 text-yellow-800
|
||||||
{{ notification.get_status_display }}
|
{% elif notification.status == 'READ' %}bg-green-100 text-green-800
|
||||||
</span>
|
{% else %}bg-gray-100 text-gray-800{% endif %}">
|
||||||
<span class="badge bg-secondary me-2">
|
{{ notification.get_status_display }}
|
||||||
{{ notification.get_notification_type_display }}
|
</span>
|
||||||
</span>
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-200 text-gray-800">
|
||||||
<small class="text-muted">{{ notification.created_at|date:"Y-m-d H:i" }}</small>
|
{{ notification.get_notification_type_display }}
|
||||||
</div>
|
</span>
|
||||||
<h6 class="mb-1">
|
<span class="text-xs text-gray-500">{{ notification.created_at|date:"Y-m-d H:i" }}</span>
|
||||||
<a href="{% url 'notification_detail' notification.id %}" class="text-decoration-none {% if notification.status == 'PENDING' %}fw-bold{% endif %}">
|
|
||||||
{{ notification.message|truncatewords:15 }}
|
|
||||||
</a>
|
|
||||||
</h6>
|
|
||||||
{% if notification.related_meeting %}
|
|
||||||
<small class="text-muted">
|
|
||||||
<i class="fas fa-video me-1"></i>
|
|
||||||
{% trans "Related to meeting:" %} {{ notification.related_meeting.topic }}
|
|
||||||
</small>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-column gap-1">
|
<h5 class="text-base font-semibold mb-1">
|
||||||
{% if notification.status == 'PENDING' %}
|
<a href="{% url 'notification_detail' notification.id %}"
|
||||||
<a href="{% url 'notification_mark_read' notification.id %}"
|
class="text-gray-900 hover:text-temple-red transition {% if notification.status == 'PENDING' %}font-bold{% endif %}">
|
||||||
class="btn btn-sm btn-outline-success"
|
{{ notification.message|truncatewords:15 }}
|
||||||
title="{% trans 'Mark as read' %}">
|
|
||||||
<i class="fas fa-check"></i>
|
|
||||||
</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'notification_mark_unread' notification.id %}"
|
|
||||||
class="btn btn-sm btn-outline-secondary"
|
|
||||||
title="{% trans 'Mark as unread' %}">
|
|
||||||
<i class="fas fa-envelope"></i>
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{% url 'notification_delete' notification.id %}"
|
|
||||||
class="btn btn-sm btn-outline-danger"
|
|
||||||
title="{% trans 'Delete notification' %}">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</h5>
|
||||||
|
{% if notification.related_meeting %}
|
||||||
|
<p class="text-sm text-gray-500">
|
||||||
|
<i data-lucide="video" class="w-3 h-3 inline mr-1"></i>
|
||||||
|
{% trans "Related to meeting:" %} {{ notification.related_meeting.topic }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
{% if notification.status == 'PENDING' %}
|
||||||
|
<a href="{% url 'notification_mark_read' notification.id %}"
|
||||||
|
class="p-2 text-green-600 hover:bg-green-50 rounded-lg transition"
|
||||||
|
title="{% trans 'Mark as read' %}">
|
||||||
|
<i data-lucide="check" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'notification_mark_unread' notification.id %}"
|
||||||
|
class="p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition"
|
||||||
|
title="{% trans 'Mark as unread' %}">
|
||||||
|
<i data-lucide="mail" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'notification_delete' notification.id %}"
|
||||||
|
class="p-2 text-red-600 hover:bg-red-50 rounded-lg transition"
|
||||||
|
title="{% trans 'Delete notification' %}">
|
||||||
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
</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,51 +1,205 @@
|
|||||||
{% load i18n crispy_forms_tags %}
|
{% load i18n crispy_forms_tags %}
|
||||||
<div class="p-3">
|
|
||||||
<form hx-boost="true" id="noteform" action="{{url}}" method="post" hx-select=".note-table-body" hx-target=".note-table-body" hx-swap="outerHTML" hx-push-url="false">
|
<div class="p-4">
|
||||||
{% csrf_token %}
|
<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|crispy}}
|
{% csrf_token %}
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" id="notesubmit" class="btn btn-outline-secondary" data-bs-dismiss="modal">{% trans "Cancel" %}</button>
|
<!-- Crispy Form for rendering -->
|
||||||
<button type="submit" class="btn btn-main-action" id="saveNoteBtn">{% trans "Save Note" %}</button>
|
<div class="space-y-4">
|
||||||
</div>
|
<div>
|
||||||
</form>
|
{{ form|crispy }}
|
||||||
<div class="table-responsive mt-3">
|
</div>
|
||||||
<table class="table table-sm" id="notesTable">
|
|
||||||
<thead>
|
<!-- Form Actions -->
|
||||||
<tr>
|
<div class="flex justify-end gap-2 pt-4 border-t-2 border-gray-200">
|
||||||
<th scope="col">{% trans "Author" %}</th>
|
|
||||||
<th scope="col" style="width: 60%;">{% trans "Note" %}</th>
|
<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">
|
||||||
<th scope="col">{% trans "Created" %}</th>
|
<i data-lucide="save" class="w-4 h-4 inline mr-1"></i>
|
||||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
{% trans "Save Note" %}
|
||||||
</tr>
|
</button>
|
||||||
</thead>
|
</div>
|
||||||
<tbody class="note-table-body">
|
</div>
|
||||||
{% if notes %}
|
</form>
|
||||||
{% for note in notes %}
|
</div>
|
||||||
<tr id="note-{{ note.id }}">
|
|
||||||
<td class="align-middle">
|
<!-- Notes Table Section -->
|
||||||
{{ note.author.get_full_name|default:note.author.username }}
|
<div class="mt-6">
|
||||||
</td>
|
<!-- Table Header -->
|
||||||
<td class="align-middle">
|
<div class="bg-gray-50 border border-gray-200 rounded-xl overflow-hidden">
|
||||||
{{ note.content|linebreaksbr }}
|
<table class="w-full border-collapse" id="notesTable">
|
||||||
</td>
|
<thead>
|
||||||
<td class="align-middle text-nowrap">
|
<tr class="border-b-2 border-temple-red">
|
||||||
<span class="text-muted">
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark">
|
||||||
{{ note.created_at|date:"SHORT_DATETIME_FORMAT" }}
|
<i data-lucide="user" class="w-3 h-3 inline mr-1"></i>
|
||||||
</span>
|
{% trans "Author" %}
|
||||||
</td>
|
</th>
|
||||||
<td class="align-middle text-end">
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark" style="width: 60%;">
|
||||||
<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">
|
<i data-lucide="sticky-note" class="w-3 h-3 inline mr-1"></i>
|
||||||
{% trans "Delete" %}
|
{% trans "Note" %}
|
||||||
</button>
|
</th>
|
||||||
|
<th class="px-4 py-3 text-left text-xs font-semibold text-temple-dark">
|
||||||
|
<i data-lucide="calendar" class="w-3 h-3 inline mr-1"></i>
|
||||||
|
{% trans "Created" %}
|
||||||
|
</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-semibold text-temple-dark">
|
||||||
|
<i data-lucide="trash-2" class="w-3 h-3 inline mr-1"></i>
|
||||||
|
{% trans "Actions" %}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="note-table-body">
|
||||||
|
{% if notes %}
|
||||||
|
{% for note in notes %}
|
||||||
|
<tr id="note-{{ note.id }}" class="hover:bg-gray-50 transition-colors border-b border-gray-200">
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<div class="w-8 h-8 rounded-full bg-temple-red text-white flex items-center justify-center text-sm font-bold">
|
||||||
|
{{ note.author.first_name.0|default:note.author.username.0|upper }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="font-medium text-temple-dark text-sm">
|
||||||
|
{{ note.author.get_full_name|default:note.author.username }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3">
|
||||||
|
<div class="text-sm text-gray-700 bg-white p-3 rounded-lg border border-gray-200 shadow-sm">
|
||||||
|
{{ note.content|linebreaksbr }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 whitespace-nowrap">
|
||||||
|
<div class="text-xs text-gray-500">
|
||||||
|
<i data-lucide="clock" class="w-3 h-3 inline mr-1"></i>
|
||||||
|
{{ note.created_at|date:"SHORT_DATETIME_FORMAT" }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-4 py-3 text-right">
|
||||||
|
<button hx-delete="{% url 'delete_note' note.slug %}"
|
||||||
|
hx-target="#note-{{ note.id }}"
|
||||||
|
hx-swap="delete"
|
||||||
|
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'
|
||||||
|
hx-confirm="{% trans 'Are you sure you want to delete this note?' %}"
|
||||||
|
type="button"
|
||||||
|
class="px-3 py-1.5 border-2 border-red-500 text-red-500 rounded-lg hover:bg-red-500 hover:text-white transition text-xs font-medium delete-note-btn inline-flex items-center gap-1"
|
||||||
|
title="{% trans 'Delete Note' %}">
|
||||||
|
<i data-lucide="trash-2" class="w-3 h-3"></i>
|
||||||
|
{% trans "Delete" %}
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="text-center py-8 text-gray-500">
|
||||||
|
<div class="flex flex-col items-center gap-2">
|
||||||
|
<i data-lucide="sticky-note" class="w-12 h-12 text-gray-400"></i>
|
||||||
|
<span class="text-sm">{% trans "No notes yet." %}</span>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
{% else %}
|
</tbody>
|
||||||
<tr>
|
</table>
|
||||||
<td colspan="4" class="text-center text-muted py-3">{% trans "No notes yet." %}</td>
|
</div>
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</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">
|
||||||
|
<i data-lucide="sticky-note" class="w-5 h-5 inline mr-2"></i>{% trans "Add Note" %}
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="text-gray-400 hover:text-gray-600 transition p-1" data-bs-dismiss="modal" aria-label="Close">
|
||||||
|
<i data-lucide="x" class="w-5 h-5"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body notemodal">
|
<div class="modal-body notemodal p-6">
|
||||||
|
<!-- Content will be loaded via HTMX -->
|
||||||
</div>
|
|
||||||
</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>
|
||||||
{% trans "This application has upcoming interviews. You are updating an existing schedule." %}
|
<p class="text-sm text-blue-800">
|
||||||
|
{% 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 %}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,170 +1,273 @@
|
|||||||
{% 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;
|
|
||||||
">{% trans "Sources Settings" %}</li>
|
|
||||||
</ol>
|
|
||||||
</nav>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
||||||
<h1 class="h3 mb-0">{% trans "Integration Sources" %}</h1>
|
|
||||||
<a href="{% url 'source_create' %}" class="btn btn-main-action">
|
|
||||||
{% trans "Create Source for Integration" %} <i class="fas fa-plus"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Search and Filters -->
|
|
||||||
<div class="card mb-4">
|
|
||||||
<div class="card-body">
|
|
||||||
<form method="get" class="row g-3">
|
|
||||||
<div class="col-md-8">
|
|
||||||
<div class="input-group">
|
|
||||||
<span class="input-group-text">
|
|
||||||
<i class="fas fa-search"></i>
|
|
||||||
</span>
|
|
||||||
<input type="text" class="form-control" name="q"
|
|
||||||
placeholder="Search sources..." value="{{ search_query }}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<button type="submit" class="btn btn-outline-primary">
|
|
||||||
<i class="fas fa-search"></i> {% trans "Search" %}
|
|
||||||
</button>
|
|
||||||
{% if search_query %}
|
|
||||||
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-times"></i> {% trans "Clear" %}
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% trans "Integration Sources" %}
|
||||||
|
</h1>
|
||||||
|
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl text-sm transition shadow-sm">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add Source" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Results Summary -->
|
<!-- Mobile Filters -->
|
||||||
{% if search_query %}
|
<div class="space-y-3">
|
||||||
<div class="alert alert-info">
|
<form method="get" class="flex gap-2">
|
||||||
Found {{ total_sources }} source{{ total_sources|pluralize }} matching "{{ search_query }}"
|
<div class="flex-1">
|
||||||
|
<div class="relative">
|
||||||
|
<i data-lucide="search" class="w-5 h-5 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2"></i>
|
||||||
|
<input type="text" name="q"
|
||||||
|
placeholder="{% trans 'Search sources...' %}"
|
||||||
|
value="{{ search_query }}"
|
||||||
|
class="w-full pl-10 pr-4 py-2.5 bg-white border border-gray-200 rounded-xl text-sm focus:ring-2 focus:ring-temple-red/20 focus:border-temple-red outline-none transition">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
<button type="submit" class="bg-temple-red hover:bg-[#7a1a29] text-white px-4 py-2.5 rounded-xl transition">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i>
|
||||||
|
</button>
|
||||||
|
{% if search_query %}
|
||||||
|
<a href="{% url 'source_list' %}" class="bg-gray-100 hover:bg-gray-200 text-gray-600 px-4 py-2.5 rounded-xl transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Sources Table -->
|
<!-- Desktop Header -->
|
||||||
<div class="card">
|
<div class="hidden lg:block">
|
||||||
<div class="card-body">
|
<!-- Breadcrumb -->
|
||||||
{% if page_obj %}
|
<nav class="mb-6" aria-label="breadcrumb">
|
||||||
<div class="table-responsive">
|
<ol class="flex items-center gap-2 text-sm flex-wrap">
|
||||||
<table class="table table-hover">
|
<li><a href="{% url 'settings' %}" class="text-gray-500 hover:text-temple-red transition flex items-center gap-1">
|
||||||
<thead class="table-light">
|
<i data-lucide="settings" class="w-4 h-4"></i> {% trans "Settings" %}
|
||||||
<tr>
|
</a></li>
|
||||||
<th>{% trans "Name" %}</th>
|
<li class="text-gray-400">/</li>
|
||||||
<th>{% trans "Type" %}</th>
|
<li class="text-temple-red font-semibold">{% trans "Sources Settings" %}</li>
|
||||||
<th>{% trans "Status" %}</th>
|
</ol>
|
||||||
<th>{% trans "API Key" %}</th>
|
</nav>
|
||||||
<th>{% trans "Created" %}</th>
|
|
||||||
<th>{% trans "Actions" %}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
|
|
||||||
{% for source in page_obj %}
|
<div class="flex justify-between items-start mb-6">
|
||||||
<tr>
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center gap-3">
|
||||||
<td>
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
||||||
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none text-primary-theme">
|
<i data-lucide="database" class="w-8 h-8 text-temple-red"></i>
|
||||||
<strong>{{ source.name }}</strong>
|
</div>
|
||||||
</a>
|
{% trans "Integration Sources" %}
|
||||||
|
</h1>
|
||||||
|
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create New Source" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
</td>
|
<!-- Desktop Filters -->
|
||||||
<td>
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
<span class="badge bg-primary-theme">{{ source.source_type }}</span>
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
</td>
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
<td>
|
<i data-lucide="filter" class="w-5 h-5"></i>
|
||||||
{% if source.is_active %}
|
{% trans "Search Sources" %}
|
||||||
<span class="badge bg-primary-theme">{% trans "Active" %}</span>
|
</h5>
|
||||||
{% else %}
|
</div>
|
||||||
<span class="badge bg-primary-theme">{% trans "Inactive" %}</span>
|
<div class="p-6">
|
||||||
{% endif %}
|
<form method="get" class="flex gap-3">
|
||||||
</td>
|
<div class="flex-1">
|
||||||
<td>
|
<div class="relative">
|
||||||
<code class="small text-primary-theme">{{ source.api_key|truncatechars:20 }}</code>
|
<i data-lucide="search" class="w-5 h-5 text-gray-400 absolute left-4 top-1/2 -translate-y-1/2"></i>
|
||||||
</td>
|
<input type="text" name="q"
|
||||||
<td>
|
placeholder="{% trans 'Search by name or type...' %}"
|
||||||
<small class="text-muted">{{ source.created_at|date:"M d, Y" }}</small>
|
value="{{ search_query }}"
|
||||||
</td>
|
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">
|
||||||
<td>
|
|
||||||
<div class="btn-group" role="group">
|
|
||||||
<a href="{% url 'source_detail' source.pk %}"
|
|
||||||
class="btn btn-sm btn-outline-primary" title="View">
|
|
||||||
<i class="fas fa-eye"></i>
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'source_update' source.pk %}"
|
|
||||||
class="btn btn-sm btn-outline-secondary" title="Edit">
|
|
||||||
<i class="fas fa-edit"></i>
|
|
||||||
</a>
|
|
||||||
{% comment %} <button type="button"
|
|
||||||
class="btn btn-sm btn-outline-warning"
|
|
||||||
hx-post="{% url 'toggle_source_status' source.pk %}"
|
|
||||||
hx-confirm="Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?"
|
|
||||||
title="{{ source.is_active|yesno:'Deactivate,Activate' }}">
|
|
||||||
<i class="fas fa-{{ source.is_active|yesno:'pause,play' }}"></i>
|
|
||||||
</button> {% endcomment %}
|
|
||||||
{% comment %} <a href="{% url 'source_delete' source.pk %}"
|
|
||||||
class="btn btn-sm btn-outline-danger" title="Delete">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</a> {% endcomment %}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</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">
|
||||||
|
<i data-lucide="search" class="w-4 h-4"></i> {% trans "Search" %}
|
||||||
|
</button>
|
||||||
|
{% if search_query %}
|
||||||
|
<a href="{% url 'source_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-100 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="x" class="w-4 h-4"></i> {% trans "Clear" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Results Summary -->
|
||||||
|
{% if search_query %}
|
||||||
|
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4 flex items-center gap-3">
|
||||||
|
<i data-lucide="info" class="w-5 h-5 text-blue-600"></i>
|
||||||
|
<p class="text-sm text-blue-800">
|
||||||
|
{% blocktrans %}Found {{ count }} source{{ count|pluralize }} matching "{{ search_query }}"{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% include "includes/paginator.html" %}
|
{# --- MOBILE CARD VIEW --- #}
|
||||||
|
<div class="lg:hidden grid grid-cols-1 gap-4">
|
||||||
|
{% for source in page_obj %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<div class="flex justify-between items-start">
|
||||||
|
<h5 class="font-bold text-lg">
|
||||||
|
<a href="{% url 'source_detail' source.pk %}" class="hover:text-white/80 transition-colors">{{ source.name }}</a>
|
||||||
|
</h5>
|
||||||
|
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2.5 py-1 rounded-full bg-white/20 backdrop-blur-sm border border-white/30">
|
||||||
|
{{ source.source_type }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-xs text-white/80 mt-1">ID: {{ source.pk }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 space-y-3">
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<i data-lucide="key" class="w-4 h-4 text-temple-red"></i>
|
||||||
|
<code class="text-xs bg-gray-100 px-2 py-1 rounded">{{ source.api_key|truncatechars:20 }}</code>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-sm text-gray-600">
|
||||||
|
<i data-lucide="calendar" class="w-4 h-4 text-temple-red"></i>
|
||||||
|
<span>{% trans "Created" %}: {{ source.created_at|date:"M d, Y" }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-sm">
|
||||||
|
<i data-lucide="activity" class="w-4 h-4 text-temple-red"></i>
|
||||||
|
{% if source.is_active %}
|
||||||
|
<span class="text-emerald-600 font-semibold">{% trans "Active" %}</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="text-center py-5">
|
<span class="text-gray-500 font-semibold">{% trans "Inactive" %}</span>
|
||||||
<i class="fas fa-database fa-3x text-muted mb-3"></i>
|
{% endif %}
|
||||||
<h5 class="text-muted">{% trans "No sources found" %}</h5>
|
</div>
|
||||||
<p class="text-muted">
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex gap-2 pt-3 border-t border-gray-100">
|
||||||
|
<a href="{% url 'source_detail' source.pk %}" class="flex-1 inline-flex items-center justify-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-medium px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i> {% trans "View Details" %}
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'source_update' source.pk %}" class="inline-flex items-center justify-center gap-2 border border-gray-300 text-gray-600 hover:bg-gray-50 px-4 py-2.5 rounded-xl text-sm transition">
|
||||||
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden text-center p-8">
|
||||||
|
<i data-lucide="database" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No sources found" %}</h3>
|
||||||
|
<p class="text-gray-500 mb-4">
|
||||||
|
{% if search_query %}
|
||||||
|
{% blocktrans %}No sources match your search criteria "{{ search_query }}".{% endblocktrans %}
|
||||||
|
{% else %}
|
||||||
|
{% trans "Get started by creating your first source." %}
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
<a href="{% url 'source_create' %}" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-5 py-2.5 rounded-xl transition shadow-sm">
|
||||||
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create Source" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# --- DESKTOP TABLE VIEW --- #}
|
||||||
|
<div class="hidden lg:block bg-white rounded-2xl shadow-sm border border-gray-100 overflow-hidden">
|
||||||
|
<div class="bg-gradient-to-br from-temple-red to-[#7a1a29] text-white p-4">
|
||||||
|
<h5 class="text-lg font-bold flex items-center gap-2">
|
||||||
|
<i data-lucide="list" class="w-5 h-5"></i>
|
||||||
|
{% trans "Source Listings" %}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Name" %}</th>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Type" %}</th>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Status" %}</th>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "API Key" %}</th>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Created" %}</th>
|
||||||
|
<th scope="col" class="px-6 py-3 text-center text-xs font-bold text-gray-700 tracking-wider whitespace-nowrap">{% trans "Actions" %}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="bg-white divide-y divide-gray-100">
|
||||||
|
{% for source in page_obj %}
|
||||||
|
<tr class="hover:bg-gray-50 transition-colors">
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<a href="{% url 'source_detail' source.pk %}" class="text-temple-red font-semibold hover:text-[#7a1a29] transition-colors">{{ source.name }}</a>
|
||||||
|
<br>
|
||||||
|
<span class="text-xs text-gray-500">ID: {{ source.pk }}</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<span class="inline-block text-[10px] font-bold uppercase tracking-wide px-2.5 py-1 rounded-full bg-temple-red/10 text-temple-red border border-temple-red">
|
||||||
|
{{ source.source_type }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
{% if source.is_active %}
|
||||||
|
<span class="inline-flex items-center gap-1.5 text-sm font-semibold text-emerald-600">
|
||||||
|
<i data-lucide="check-circle-2" class="w-4 h-4"></i> {% trans "Active" %}
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="inline-flex items-center gap-1.5 text-sm font-semibold text-gray-500">
|
||||||
|
<i data-lucide="x-circle" class="w-4 h-4"></i> {% trans "Inactive" %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<code class="text-xs bg-gray-100 px-2 py-1 rounded text-temple-red">{{ source.api_key|truncatechars:20 }}</code>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 text-sm text-gray-700">{{ source.created_at|date:"M d, Y" }}</td>
|
||||||
|
<td class="px-6 py-4">
|
||||||
|
<div class="flex items-center gap-2 justify-center">
|
||||||
|
<a href="{% url 'source_detail' source.pk %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg bg-temple-red hover:bg-[#7a1a29] text-white transition" title="{% trans 'View' %}">
|
||||||
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'source_update' source.pk %}" class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:bg-gray-100 text-gray-600 transition" title="{% trans 'Edit' %}">
|
||||||
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="px-6 py-12 text-center">
|
||||||
|
<i data-lucide="database" class="w-16 h-16 text-temple-red mx-auto mb-4"></i>
|
||||||
|
<h3 class="text-xl font-semibold text-gray-900 mb-2">{% trans "No sources found" %}</h3>
|
||||||
|
<p class="text-gray-500 mb-4">
|
||||||
{% if search_query %}
|
{% if search_query %}
|
||||||
{% blocktrans with query=query %}No sources match your search criteria "{{ query }}".{% endblocktrans %}
|
{% blocktrans %}No sources match your search criteria "{{ search_query }}".{% endblocktrans %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% trans "Get started by creating your first source." %}
|
{% trans "Get started by creating your first source." %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<a href="{% url 'source_create' %}" class="btn btn-main-action">
|
<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 class="fas fa-plus"></i> {% trans "Create Source" %}
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Create Source" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</td>
|
||||||
{% endif %}
|
</tr>
|
||||||
</div>
|
{% endfor %}
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block customJS %}
|
{% include "includes/paginator.html" %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Auto-refresh after status toggle
|
lucide.createIcons();
|
||||||
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
|
||||||
if (evt.detail.successful) {
|
// Auto-refresh after status toggle (for HTMX)
|
||||||
// Reload the page after a short delay to show updated status
|
document.body.addEventListener('htmx:afterRequest', function(evt) {
|
||||||
setTimeout(() => {
|
if (evt.detail.successful) {
|
||||||
window.location.reload();
|
setTimeout(() => {
|
||||||
}, 500);
|
window.location.reload();
|
||||||
}
|
}, 500);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Loading…
x
Reference in New Issue
Block a user