Compare commits

...

4 Commits

Author SHA1 Message Date
ec3c52579b merge complete 2025-10-22 16:06:18 +03:00
ba911a60d6 small update 2025-10-22 16:03:05 +03:00
f0d3218caa few ui changes' 2025-10-22 15:58:21 +03:00
05e3271152 new chnage 2025-10-22 14:38:42 +03:00
41 changed files with 697 additions and 847 deletions

View File

@ -1,11 +1,12 @@
from recruitment import views
from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from django.conf.urls.static import static from django.conf.urls.static import static
from django.conf import settings from django.views.generic import RedirectView
from django.conf.urls.i18n import i18n_patterns # Import i18n_patterns from django.conf.urls.i18n import i18n_patterns
from rest_framework.routers import DefaultRouter from rest_framework.routers import DefaultRouter
from recruitment import views
router = DefaultRouter() router = DefaultRouter()
router.register(r'jobs', views.JobPostingViewSet) router.register(r'jobs', views.JobPostingViewSet)
@ -22,8 +23,8 @@ urlpatterns = [
# path('', include('recruitment.urls')), # path('', include('recruitment.urls')),
path("ckeditor5/", include('django_ckeditor_5.urls')), path("ckeditor5/", include('django_ckeditor_5.urls')),
path('<slug:template_slug>/', views.form_wizard_view, name='form_wizard'), path('form/<slug:template_slug>/', views.form_wizard_view, name='form_wizard'),
path('<slug:template_slug>/submit/', views.submit_form, name='submit_form'), path('form/<slug:template_slug>/submit/', views.submit_form, name='submit_form'),
path('api/templates/', views.list_form_templates, name='list_form_templates'), path('api/templates/', views.list_form_templates, name='list_form_templates'),
path('api/templates/save/', views.save_form_template, name='save_form_template'), path('api/templates/save/', views.save_form_template, name='save_form_template'),
@ -34,7 +35,6 @@ urlpatterns = [
urlpatterns += i18n_patterns( urlpatterns += i18n_patterns(
path('', include('recruitment.urls')), path('', include('recruitment.urls')),
) )
# 2. URLs that DO have a language prefix (user-facing views) # 2. URLs that DO have a language prefix (user-facing views)
# This includes the root path (''), which is handled by 'recruitment.urls' # This includes the root path (''), which is handled by 'recruitment.urls'

View File

@ -290,6 +290,7 @@ class TrainingMaterialForm(forms.ModelForm):
) )
) )
class JobPostingForm(forms.ModelForm): class JobPostingForm(forms.ModelForm):
"""Form for creating and editing job postings""" """Form for creating and editing job postings"""
@ -298,12 +299,13 @@ class JobPostingForm(forms.ModelForm):
fields = [ fields = [
'title', 'department', 'job_type', 'workplace_type', 'title', 'department', 'job_type', 'workplace_type',
'location_city', 'location_state', 'location_country', 'location_city', 'location_state', 'location_country',
'description', 'qualifications', 'salary_range', 'benefits','application_start_date' 'description', 'qualifications', 'salary_range', 'benefits',
,'application_deadline', 'application_instructions', 'application_deadline', 'application_instructions',
'position_number', 'reporting_to', 'joining_date', 'position_number', 'reporting_to',
'created_by','open_positions','hash_tags','max_applications' 'open_positions', 'hash_tags', 'max_applications'
] ]
widgets = { widgets = {
# Basic Information
'title': forms.TextInput(attrs={ 'title': forms.TextInput(attrs={
'class': 'form-control', 'class': 'form-control',
'placeholder': 'Assistant Professor of Computer Science', 'placeholder': 'Assistant Professor of Computer Science',
@ -321,6 +323,8 @@ class JobPostingForm(forms.ModelForm):
'class': 'form-select', 'class': 'form-select',
'required': True 'required': True
}), }),
# Location
'location_city': forms.TextInput(attrs={ 'location_city': forms.TextInput(attrs={
'class': 'form-control', 'class': 'form-control',
'placeholder': 'Boston' 'placeholder': 'Boston'
@ -333,18 +337,25 @@ class JobPostingForm(forms.ModelForm):
'class': 'form-control', 'class': 'form-control',
'value': 'United States' 'value': 'United States'
}), }),
'salary_range': forms.TextInput(attrs={ 'salary_range': forms.TextInput(attrs={
'class': 'form-control', 'class': 'form-control',
'placeholder': '$60,000 - $80,000' 'placeholder': '$60,000 - $80,000'
}), }),
'application_start_date': forms.DateInput(attrs={
'class': 'form-control', # Application Information
'type': 'date' # 'application_url': forms.URLInput(attrs={
}), # 'class': 'form-control',
# 'placeholder': 'https://university.edu/careers/job123',
# 'required': True
# }),
'application_deadline': forms.DateInput(attrs={ 'application_deadline': forms.DateInput(attrs={
'class': 'form-control', 'class': 'form-control',
'type': 'date' 'type': 'date',
'required': True
}), }),
'open_positions': forms.NumberInput(attrs={ 'open_positions': forms.NumberInput(attrs={
'class': 'form-control', 'class': 'form-control',
'min': 1, 'min': 1,
@ -353,7 +364,10 @@ class JobPostingForm(forms.ModelForm):
'hash_tags': forms.TextInput(attrs={ 'hash_tags': forms.TextInput(attrs={
'class': 'form-control', 'class': 'form-control',
'placeholder': '#hiring,#jobopening', 'placeholder': '#hiring,#jobopening',
# 'validators':validate_hash_tags, # Assuming this is available
}), }),
# Internal Information
'position_number': forms.TextInput(attrs={ 'position_number': forms.TextInput(attrs={
'class': 'form-control', 'class': 'form-control',
'placeholder': 'UNIV-2025-001' 'placeholder': 'UNIV-2025-001'
@ -362,14 +376,7 @@ class JobPostingForm(forms.ModelForm):
'class': 'form-control', 'class': 'form-control',
'placeholder': 'Department Chair, Director, etc.' 'placeholder': 'Department Chair, Director, etc.'
}), }),
'joining_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'created_by': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'University Administrator'
}),
'max_applications': forms.NumberInput(attrs={ 'max_applications': forms.NumberInput(attrs={
'class': 'form-control', 'class': 'form-control',
'min': 1, 'min': 1,
@ -377,40 +384,51 @@ class JobPostingForm(forms.ModelForm):
}), }),
} }
def __init__(self,*args,**kwargs): def __init__(self, *args, **kwargs):
self.is_anonymous_user = kwargs.pop('is_anonymous_user', False)
# Now call the parent __init__ with remaining args
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if not self.instance.pk: if not self.instance.pk: # Creating new job posting
if not self.is_anonymous_user: # self.fields['status'].initial = 'Draft'
self.fields['created_by'].initial = 'University Administrator' self.fields['location_city'].initial = 'Riyadh'
self.fields['location_city'].initial='Riyadh' self.fields['location_state'].initial = 'Riyadh Province'
self.fields['location_state'].initial='Riyadh Province' self.fields['location_country'].initial = 'Saudi Arabia'
self.fields['location_country'].initial='Saudi Arabia'
def clean_hash_tags(self): def clean_hash_tags(self):
hash_tags=self.cleaned_data.get('hash_tags') hash_tags = self.cleaned_data.get('hash_tags')
if hash_tags: if hash_tags:
tags=[tag.strip() for tag in hash_tags.split(',') if tag.strip()] tags = [tag.strip() for tag in hash_tags.split(',') if tag.strip()]
for tag in tags: for tag in tags:
if not tag.startswith('#'): if not tag.startswith('#'):
raise forms.ValidationError("Each hashtag must start with '#' symbol and must be comma(,) sepearted.") raise forms.ValidationError(
"Each hashtag must start with '#' symbol and must be comma(,) sepearted.")
return ','.join(tags) return ','.join(tags)
return hash_tags return hash_tags # Allow blank
def clean_title(self): def clean_title(self):
title=self.cleaned_data.get('title') title = self.cleaned_data.get('title')
if not title or len(title.strip())<3: if not title or len(title.strip()) < 3:
raise forms.ValidationError("Job title must be at least 3 characters long.") raise forms.ValidationError("Job title must be at least 3 characters long.")
if len(title)>200: if len(title) > 200:
raise forms.ValidationError("Job title cannot exceed 200 characters.") raise forms.ValidationError("Job title cannot exceed 200 characters.")
return title.strip() return title.strip()
def clean_description(self): def clean_description(self):
description=self.cleaned_data.get('description') description = self.cleaned_data.get('description')
if not description or len(description.strip())<20: if not description or len(description.strip()) < 20:
raise forms.ValidationError("Job description must be at least 20 characters long.") raise forms.ValidationError("Job description must be at least 20 characters long.")
return description.strip() return description.strip() # to remove leading/trailing whitespace
def clean_application_url(self):
url = self.cleaned_data.get('application_url')
if url:
validator = URLValidator()
try:
validator(url)
except forms.ValidationError:
raise forms.ValidationError('Please enter a valid URL (e.g., https://example.com)')
return url
class JobPostingImageForm(forms.ModelForm): class JobPostingImageForm(forms.ModelForm):
class Meta: class Meta:

View File

@ -33,6 +33,7 @@ class Profile(models.Model):
def __str__(self): def __str__(self):
return f"image for user {self.user}" return f"image for user {self.user}"
class JobPosting(Base): class JobPosting(Base):
# Basic Job Information # Basic Job Information
JOB_TYPES = [ JOB_TYPES = [
@ -66,16 +67,16 @@ class JobPosting(Base):
# Job Details # Job Details
description = CKEditor5Field( description = CKEditor5Field(
'Description', 'Description',
config_name='extends' # Matches the config name you defined in settings.py config_name='extends' # Matches the config name you defined in settings.py
) )
qualifications = CKEditor5Field(blank=True,null=True, qualifications = CKEditor5Field(blank=True, null=True,
config_name='extends' config_name='extends'
) )
salary_range = models.CharField( salary_range = models.CharField(
max_length=200, blank=True, help_text="e.g., $60,000 - $80,000" max_length=200, blank=True, help_text="e.g., $60,000 - $80,000"
) )
benefits = CKEditor5Field(blank=True,null=True,config_name='extends') benefits = CKEditor5Field(blank=True, null=True, config_name='extends')
# Application Information ---job detail apply link for the candidates # Application Information ---job detail apply link for the candidates
application_url = models.URLField( application_url = models.URLField(
@ -84,10 +85,10 @@ class JobPosting(Base):
null=True, null=True,
blank=True, blank=True,
) )
application_start_date=models.DateField(null=True, blank=True)
application_deadline = models.DateField(db_index=True, null=True, blank=True) # Added index application_deadline = models.DateField(db_index=True) # Added index
application_instructions =CKEditor5Field( application_instructions = CKEditor5Field(
blank=True, null=True,config_name='extends' blank=True, null=True, config_name='extends'
) )
# Internal Tracking # Internal Tracking
@ -105,7 +106,7 @@ class JobPosting(Base):
("ARCHIVED", "Archived"), ("ARCHIVED", "Archived"),
] ]
status = models.CharField( status = models.CharField(
db_index=True, max_length=20, choices=STATUS_CHOICES, default="DRAFT" # Added index db_index=True, max_length=20, choices=STATUS_CHOICES, default="DRAFT" # Added index
) )
# hashtags for social media # hashtags for social media
@ -129,7 +130,7 @@ class JobPosting(Base):
) )
linkedin_posted_at = models.DateTimeField(null=True, blank=True) linkedin_posted_at = models.DateTimeField(null=True, blank=True)
published_at = models.DateTimeField(db_index=True, null=True, blank=True) # Added index published_at = models.DateTimeField(db_index=True, null=True, blank=True) # Added index
# University Specific Fields # University Specific Fields
position_number = models.CharField( position_number = models.CharField(
max_length=50, blank=True, help_text="University position number" max_length=50, blank=True, help_text="University position number"
@ -137,7 +138,7 @@ class JobPosting(Base):
reporting_to = models.CharField( reporting_to = models.CharField(
max_length=100, blank=True, help_text="Who this position reports to" max_length=100, blank=True, help_text="Who this position reports to"
) )
joining_date = models.DateField(null=True, blank=True, help_text="Desired start date")
open_positions = models.PositiveIntegerField( open_positions = models.PositiveIntegerField(
default=1, help_text="Number of open positions for this job" default=1, help_text="Number of open positions for this job"
) )
@ -149,11 +150,11 @@ class JobPosting(Base):
null=True, null=True,
blank=True, blank=True,
help_text="The system or channel from which this job posting originated or was first published.", help_text="The system or channel from which this job posting originated or was first published.",
db_index=True # Explicitly index ForeignKey db_index=True # Explicitly index ForeignKey
) )
max_applications = models.PositiveIntegerField( max_applications = models.PositiveIntegerField(
default=1000, help_text="Maximum number of applications allowed",null=True,blank=True default=1000, help_text="Maximum number of applications allowed", null=True, blank=True
) )
hiring_agency = models.ManyToManyField( hiring_agency = models.ManyToManyField(
"HiringAgency", "HiringAgency",
blank=True, blank=True,
@ -181,7 +182,7 @@ class JobPosting(Base):
verbose_name = "Job Posting" verbose_name = "Job Posting"
verbose_name_plural = "Job Postings" verbose_name_plural = "Job Postings"
indexes = [ indexes = [
models.Index(fields=['status', 'created_at','title']), models.Index(fields=['status', 'created_at', 'title']),
models.Index(fields=['slug']), models.Index(fields=['slug']),
] ]
@ -258,7 +259,6 @@ class JobPosting(Base):
# Return True if any content remains after stripping tags and spaces. # Return True if any content remains after stripping tags and spaces.
return bool(final_content) return bool(final_content)
@property @property
def has_description_content(self): def has_description_content(self):
"""Returns True if the description field has meaningful content.""" """Returns True if the description field has meaningful content."""
@ -277,6 +277,7 @@ class JobPosting(Base):
@property @property
def has_application_instructions_content(self): def has_application_instructions_content(self):
return self._check_content(self.application_instructions) return self._check_content(self.application_instructions)
@property @property
def current_applications_count(self): def current_applications_count(self):
"""Returns the current number of candidates associated with this job.""" """Returns the current number of candidates associated with this job."""
@ -289,9 +290,13 @@ class JobPosting(Base):
return True return True
return self.current_applications_count >= self.max_applications return self.current_applications_count >= self.max_applications
@property @property
def all_candidates(self): def all_candidates(self):
return self.candidates.annotate(sortable_score=Coalesce(Cast('ai_analysis_data__analysis_data__match_score',output_field=IntegerField()),0)).order_by('-sortable_score') return self.candidates.annotate(
sortable_score=Coalesce(Cast('ai_analysis_data__analysis_data__match_score', output_field=IntegerField()),
0)).order_by('-sortable_score')
@property @property
def screening_candidates(self): def screening_candidates(self):
return self.all_candidates.filter(stage="Applied") return self.all_candidates.filter(stage="Applied")
@ -299,6 +304,7 @@ class JobPosting(Base):
@property @property
def exam_candidates(self): def exam_candidates(self):
return self.all_candidates.filter(stage="Exam") return self.all_candidates.filter(stage="Exam")
@property @property
def interview_candidates(self): def interview_candidates(self):
return self.all_candidates.filter(stage="Interview") return self.all_candidates.filter(stage="Interview")
@ -307,11 +313,13 @@ class JobPosting(Base):
def offer_candidates(self): def offer_candidates(self):
return self.all_candidates.filter(stage="Offer") return self.all_candidates.filter(stage="Offer")
# counts
#counts
@property @property
def all_candidates_count(self): def all_candidates_count(self):
return self.candidates.annotate(sortable_score=Cast('ai_analysis_data__match_score',output_field=CharField())).order_by('-sortable_score').count() return self.candidates.annotate(
sortable_score=Cast('ai_analysis_data__match_score', output_field=CharField())).order_by(
'-sortable_score').count()
@property @property
def screening_candidates_count(self): def screening_candidates_count(self):
return self.all_candidates.filter(stage="Applied").count() return self.all_candidates.filter(stage="Applied").count()
@ -319,6 +327,7 @@ class JobPosting(Base):
@property @property
def exam_candidates_count(self): def exam_candidates_count(self):
return self.all_candidates.filter(stage="Exam").count() return self.all_candidates.filter(stage="Exam").count()
@property @property
def interview_candidates_count(self): def interview_candidates_count(self):
return self.all_candidates.filter(stage="Interview").count() return self.all_candidates.filter(stage="Interview").count()
@ -327,6 +336,7 @@ class JobPosting(Base):
def offer_candidates_count(self): def offer_candidates_count(self):
return self.all_candidates.filter(stage="Offer").count() return self.all_candidates.filter(stage="Offer").count()
class JobPostingImage(models.Model): class JobPostingImage(models.Model):
job=models.OneToOneField('JobPosting',on_delete=models.CASCADE,related_name='post_images') job=models.OneToOneField('JobPosting',on_delete=models.CASCADE,related_name='post_images')
post_image = models.ImageField(upload_to='post/',validators=[validate_image_size]) post_image = models.ImageField(upload_to='post/',validators=[validate_image_size])

View File

@ -1,5 +1,8 @@
import logging import logging
from django.db import transaction from django.db import transaction
from django_q.models import Schedule
from django_q.tasks import schedule
from django.dispatch import receiver from django.dispatch import receiver
from django_q.tasks import async_task from django_q.tasks import async_task
from django.db.models.signals import post_save from django.db.models.signals import post_save
@ -16,7 +19,31 @@ def format_job(sender, instance, created, **kwargs):
instance.pk, instance.pk,
# hook='myapp.tasks.email_sent_callback' # Optional callback # hook='myapp.tasks.email_sent_callback' # Optional callback
) )
else:
existing_schedule = Schedule.objects.filter(
func='recruitment.tasks.form_close',
args=f'[{instance.pk}]',
schedule_type=Schedule.ONCE
).first()
if instance.is_active and instance.application_deadline:
if not existing_schedule:
# Create a new schedule if one does not exist
schedule(
'recruitment.tasks.form_close',
instance.pk,
schedule_type=Schedule.ONCE,
next_run=instance.application_deadline,
repeats=-1, # Ensure the schedule is deleted after it runs
name=f'job_closing_{instance.pk}' # Add a name for easier lookup
)
elif existing_schedule.next_run != instance.application_deadline:
# Update an existing schedule's run time
existing_schedule.next_run = instance.application_deadline
existing_schedule.save()
elif existing_schedule:
# If the instance is no longer active, delete the scheduled task
existing_schedule.delete()
@receiver(post_save, sender=Candidate) @receiver(post_save, sender=Candidate)
def score_candidate_resume(sender, instance, created, **kwargs): def score_candidate_resume(sender, instance, created, **kwargs):

View File

@ -552,3 +552,10 @@ def linkedin_post_task(job_slug, access_token):
job.linkedin_post_status = f"CRITICAL_ERROR: {str(e)}" job.linkedin_post_status = f"CRITICAL_ERROR: {str(e)}"
job.save() job.save()
return False return False
def form_close(job_id):
job = get_object_or_404(JobPosting, pk=job_id)
job.is_active = False
job.template_form.is_active = False
job.save()

View File

@ -273,26 +273,17 @@ def create_job(request):
if request.method == "POST": if request.method == "POST":
form = JobPostingForm( form = JobPostingForm(
request.POST, is_anonymous_user=not request.user.is_authenticated request.POST
) )
# to check user is authenticated or not # to check user is authenticated or not
if form.is_valid(): if form.is_valid():
try: try:
job = form.save(commit=False) job = form.save(commit=False)
if request.user.is_authenticated:
job.created_by = (
request.user.get_full_name() or request.user.username
)
else:
job.created_by = request.POST.get("created_by", "").strip()
if not job.created_by:
job.created_by = request.user.username
job.save() job.save()
job_apply_url_relative=reverse('job_detail_candidate',kwargs={'slug':job.slug}) job_apply_url_relative=reverse('job_detail_candidate',kwargs={'slug':job.slug})
job_apply_url_absolute=request.build_absolute_uri(job_apply_url_relative) job_apply_url_absolute=request.build_absolute_uri(job_apply_url_relative)
job.application_url=job_apply_url_absolute job.application_url=job_apply_url_absolute
FormTemplate.objects.create(job=job, is_active=True, name=job.title,created_by=request.user) # FormTemplate.objects.create(job=job, is_active=False, name=job.title,created_by=request.user)
job.save() job.save()
messages.success(request, f'Job "{job.title}" created successfully!') messages.success(request, f'Job "{job.title}" created successfully!')
return redirect("job_list") return redirect("job_list")
@ -302,7 +293,7 @@ def create_job(request):
else: else:
messages.error(request, f"Please correct the errors below.{form.errors}") messages.error(request, f"Please correct the errors below.{form.errors}")
else: else:
form = JobPostingForm(is_anonymous_user=not request.user.is_authenticated) form = JobPostingForm()
return render(request, "jobs/create_job.html", {"form": form}) return render(request, "jobs/create_job.html", {"form": form})
@ -313,21 +304,11 @@ def edit_job(request, slug):
if request.method == "POST": if request.method == "POST":
form = JobPostingForm( form = JobPostingForm(
request.POST, request.POST,
instance=job, instance=job
is_anonymous_user=not request.user.is_authenticated,
) )
if form.is_valid(): if form.is_valid():
try: try:
job = form.save(commit=False) form.save()
if request.user.is_authenticated:
job.created_by = (
request.user.get_full_name() or request.user.username
)
else:
job.created_by = request.POST.get("created_by", "").strip()
if not job.created_by:
job.created_by = "University Administrator"
job.save()
messages.success(request, f'Job "{job.title}" updated successfully!') messages.success(request, f'Job "{job.title}" updated successfully!')
return redirect("job_list") return redirect("job_list")
except Exception as e: except Exception as e:
@ -338,7 +319,7 @@ def edit_job(request, slug):
else: else:
job = get_object_or_404(JobPosting, slug=slug) job = get_object_or_404(JobPosting, slug=slug)
form = JobPostingForm( form = JobPostingForm(
instance=job, is_anonymous_user=not request.user.is_authenticated instance=job
) )
return render(request, "jobs/edit_job.html", {"form": form, "job": job}) return render(request, "jobs/edit_job.html", {"form": form, "job": job})
@ -2224,10 +2205,10 @@ def account_toggle_status(request,pk):
messages.error(f'Please correct the error below') messages.error(f'Please correct the error below')
@login_required # @login_required
def user_detail(requests,pk): # def user_detail(requests,pk):
user=get_object_or_404(User,pk=pk) # user=get_object_or_404(User,pk=pk)
return render(requests,'user/profile.html') # return render(requests,'user/profile.html')
@csrf_exempt @csrf_exempt

View File

@ -44,7 +44,7 @@
<div class="en text-xs">King Abdullah bin Abdulaziz University Hospital</div> <div class="en text-xs">King Abdullah bin Abdulaziz University Hospital</div>
</div> </div>
</div> </div>
<img src="{% static 'image/hospital_logo.png' %}" alt="KAAUH Logo" style="max-height: 40px; max-width: 40px;"> <img src="{% static 'image/kaauh.png' %}" alt="KAAUH Logo" style="max-height: 40px; max-width: 40px;">
</div> </div>
</div> </div>
</div> </div>
@ -55,11 +55,11 @@
{# --- MOBILE BRAND LOGIC: Show small logo on mobile, large on desktop (lg) --- #} {# --- MOBILE BRAND LOGIC: Show small logo on mobile, large on desktop (lg) --- #}
<a class="navbar-brand text-white d-block d-lg-none" href="{% url 'dashboard' %}" aria-label="Home"> <a class="navbar-brand text-white d-block d-lg-none" href="{% url 'dashboard' %}" aria-label="Home">
<img src="{% static 'image/hospital_logo_1.png' %}" alt="{% trans 'kaauh logo green bg' %}" class="navbar-brand-mobile"> <img src="{% static 'image/kaauh_green1.png' %}" alt="{% trans 'kaauh logo green bg' %}" class="navbar-brand-mobile">
</a> </a>
<a class="navbar-brand text-white d-none d-lg-block me-4 pe-4" href="{% url 'dashboard' %}" aria-label="Home"> <a class="navbar-brand text-white d-none d-lg-block me-4 pe-4" href="{% url 'dashboard' %}" aria-label="Home">
<img src="{% static 'image/hospital_logo_1.png' %}" alt="{% trans 'kaauh logo green bg' %}" style="width: 60px; height: 60px;"> <img src="{% static 'image/kaauh_green1.png' %}" alt="{% trans 'kaauh logo green bg' %}" style="width: 60px; height: 60px;">
</a> </a>
{# Toggler: order-lg-0 ensures it's before navigation links on desktop, but it stays where it is on mobile #} {# Toggler: order-lg-0 ensures it's before navigation links on desktop, but it stays where it is on mobile #}
@ -216,21 +216,13 @@
<li class="nav-item me-lg-4"> <li class="nav-item me-lg-4">
<a class="nav-link {% if request.resolver_match.url_name == 'list_meetings' %}active{% endif %}" href="{% url 'list_meetings' %}"> <a class="nav-link {% if request.resolver_match.url_name == 'list_meetings' %}active{% endif %}" href="{% url 'list_meetings' %}">
<span class="d-flex align-items-center gap-2"> <span class="d-flex align-items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
<path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" /> <path stroke-linecap="round" stroke-linejoin="round" d="m15.75 10.5 4.72-4.72a.75.75 0 0 1 1.28.53v11.38a.75.75 0 0 1-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 0 0 2.25-2.25v-9a2.25 2.25 0 0 0-2.25-2.25h-9A2.25 2.25 0 0 0 2.25 7.5v9a2.25 2.25 0 0 0 2.25 2.25Z" />
</svg> </svg>
{% trans "Meetings" %} {% trans "Meetings" %}
</span> </span>
</a> </a>
</li> </li>
<li class="nav-item me-lg-4">
<a class="nav-link {% if request.resolver_match.url_name == 'source_list' %}active{% endif %}" href="{% url 'source_list' %}">
<span class="d-flex align-items-center gap-2">
{% include "icons/sources.html" %}
{% trans "Sources" %}
</span>
</a>
</li>
{% comment %} <li class="nav-item me-lg-4"> {% comment %} <li class="nav-item me-lg-4">
<a class="nav-link {% if request.resolver_match.url_name == 'training_list' %}active{% endif %}" href="{% url 'training_list' %}"> <a class="nav-link {% if request.resolver_match.url_name == 'training_list' %}active{% endif %}" href="{% url 'training_list' %}">
<span class="d-flex align-items-center gap-2"> <span class="d-flex align-items-center gap-2">
@ -352,7 +344,7 @@
}); });
} }
form_loader(); //form_loader();
try{ try{
document.addEventListener('htmx:afterSwap', form_loader); document.addEventListener('htmx:afterSwap', form_loader);
@ -364,4 +356,4 @@
{% block customJS %}{% endblock %} {% block customJS %}{% endblock %}
</body> </body>
</html> </html>

View File

@ -824,7 +824,7 @@
}); });
try { try {
const response = await fetch(`/${state.templateId}/submit/`, { const response = await fetch(`/form/${state.templateId}/submit/`, {
method: 'POST', method: 'POST',
body: formData body: formData
// IMPORTANT: Do NOT set Content-Type header when using FormData // IMPORTANT: Do NOT set Content-Type header when using FormData

View File

@ -71,7 +71,7 @@
border: 1px solid #ced4da; border: 1px solid #ced4da;
width: 100%; width: 100%;
padding: 0.375rem 0.75rem; padding: 0.375rem 0.75rem;
box-sizing: border-box; box-sizing: border-box;
} }
/* ================================================= */ /* ================================================= */
@ -100,7 +100,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container-fluid py-4">
<h1 class="h3 mb-4 text-primary fw-bold"> <h1 class="h3 mb-4 text-primary fw-bold">
<i class="fas fa-bullhorn me-2"></i> {% if form.instance.pk %} {% trans "Edit Job Posting" %} {% else %} {% trans "Create New Job Posting" %} {% endif %} <i class="fas fa-bullhorn me-2"></i> {% if form.instance.pk %} {% trans "Edit Job Posting" %} {% else %} {% trans "Create New Job Posting" %} {% endif %}
</h1> </h1>
@ -150,8 +150,113 @@
</div> </div>
</div> </div>
{# ================================================= #} {# ================================================= #}
{# SECTION 2: JOB CONTENT (CKEDITOR 5 Fields) #} {# SECTION 2: INTERNAL AND PROMOTION #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-tags"></i> {% trans "Internal & Promotion" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.position_number.id_for_label }}" class="form-label">{% trans "Position Number" %}</label>
{{ form.position_number }}
{% if form.position_number.errors %}<div class="text-danger small mt-1">{{ form.position_number.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">{% trans "Reports To" %}</label>
{{ form.reporting_to }}
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
{{ form.open_positions }}
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
{{ form.max_applications }}
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
</div>
</div>
<div class="col-12">
<div>
<label for="{{ form.hash_tags.id_for_label }}" class="form-label">{% trans "Hashtags (For Promotion/Search on Linkedin)" %}</label>
{{ form.hash_tags }}
{% if form.hash_tags.errors %}<div class="text-danger small mt-1">{{ form.hash_tags.errors }}</div>{% endif %}
<div class="form-text">{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}</div>
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 3: LOCATION AND DATES #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-map-marker-alt"></i> {% trans "Location, Dates, & Salary" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-4">
<div>
<label for="{{ form.location_city.id_for_label }}" class="form-label">{% trans "City" %}</label>
{{ form.location_city }}
{% if form.location_city.errors %}<div class="text-danger small mt-1">{{ form.location_city.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_state.id_for_label }}" class="form-label">{% trans "State/Province" %}</label>
{{ form.location_state }}
{% if form.location_state.errors %}<div class="text-danger small mt-1">{{ form.location_state.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_country.id_for_label }}" class="form-label">{% trans "Country" %}</label>
{{ form.location_country }}
{% if form.location_country.errors %}<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}<span class="text-danger">*</span></label>
{{ form.application_deadline }}
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.salary_range.id_for_label }}" class="form-label">{% trans "Salary Range" %}</label>
{{ form.salary_range }}
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 4: JOB CONTENT (CKEDITOR 5 Fields) #}
{# ================================================= #} {# ================================================= #}
<div class="card mb-4 shadow-sm"> <div class="card mb-4 shadow-sm">
<div class="card-header-themed"> <div class="card-header-themed">
@ -179,21 +284,15 @@
</div> </div>
{# ================================================= #} {# ================================================= #}
{# SECTION 3: COMPENSATION AND APPLICATION #} {# SECTION 5: APPLICATION Instructions #}
{# ================================================= #} {# ================================================= #}
<div class="card mb-4 shadow-sm"> <div class="card mb-4 shadow-sm">
<div class="card-header-themed"> <div class="card-header-themed">
<h5><i class="fas fa-dollar-sign"></i> {% trans "Compensation & Application" %}</h5> <h5><i class="fas fa-dollar-sign"></i> {% trans "Benefits & Application Instructions" %}</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row g-4"> <div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.salary_range.id_for_label }}" class="form-label">{% trans "Salary Range" %}</label>
{{ form.salary_range }}
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
</div>
</div>
{% comment %} (application_url comment removed for brevity) {% endcomment %} {% comment %} (application_url comment removed for brevity) {% endcomment %}
<div class="col-12"> <div class="col-12">
@ -215,117 +314,7 @@
</div> </div>
</div> </div>
{# ================================================= #}
{# SECTION 4: LOCATION AND DATES #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-map-marker-alt"></i> {% trans "Location, Dates, & Status" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-4">
<div>
<label for="{{ form.location_city.id_for_label }}" class="form-label">{% trans "City" %}</label>
{{ form.location_city }}
{% if form.location_city.errors %}<div class="text-danger small mt-1">{{ form.location_city.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_state.id_for_label }}" class="form-label">{% trans "State/Province" %}</label>
{{ form.location_state }}
{% if form.location_state.errors %}<div class="text-danger small mt-1">{{ form.location_state.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_country.id_for_label }}" class="form-label">{% trans "Country" %}</label>
{{ form.location_country }}
{% if form.location_country.errors %}<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}</label>
{{ form.application_deadline }}
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.application_start_date.id_for_label }}" class="form-label">{% trans "Application Start Date" %}</label>
{{ form.application_start_date }}
{% if form.application_start_date.errors %}<div class="text-danger small mt-1">{{ form.application_start_date.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.joining_date.id_for_label }}" class="form-label">{% trans "Desired Joining Date" %}</label>
{{ form.joining_date }}
{% if form.joining_date.errors %}<div class="text-danger small mt-1">{{ form.joining_date.errors }}</div>{% endif %}
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 5: INTERNAL AND PROMOTION #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-tags"></i> {% trans "Internal & Promotion" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.position_number.id_for_label }}" class="form-label">{% trans "Position Number" %}</label>
{{ form.position_number }}
{% if form.position_number.errors %}<div class="text-danger small mt-1">{{ form.position_number.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">{% trans "Reports To" %}</label>
{{ form.reporting_to }}
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
{{ form.open_positions }}
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
{{ form.max_applications }}
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.created_by.id_for_label }}" class="form-label">{% trans "Created By" %}</label>
{{ form.created_by }}
{% if form.created_by.errors %}<div class="text-danger small mt-1">{{ form.created_by.errors }}</div>{% endif %}
</div>
</div>
<div class="col-12">
<div>
<label for="{{ form.hash_tags.id_for_label }}" class="form-label">{% trans "Hashtags (For Promotion/Search)" %}</label>
{{ form.hash_tags }}
{% if form.hash_tags.errors %}<div class="text-danger small mt-1">{{ form.hash_tags.errors }}</div>{% endif %}
<div class="form-text">{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}</div>
</div>
</div>
</div>
</div>
</div>
{# ================================================= #} {# ================================================= #}
{# ACTION BUTTONS #} {# ACTION BUTTONS #}

View File

@ -71,7 +71,7 @@
border: 1px solid #ced4da; border: 1px solid #ced4da;
width: 100%; width: 100%;
padding: 0.375rem 0.75rem; padding: 0.375rem 0.75rem;
box-sizing: border-box; box-sizing: border-box;
} }
/* ================================================= */ /* ================================================= */
@ -100,7 +100,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container-fluid py-4">
<h1 class="h3 mb-4 text-primary fw-bold"> <h1 class="h3 mb-4 text-primary fw-bold">
<i class="fas fa-bullhorn me-2"></i> {% if form.instance.pk %} {% trans "Edit Job Posting" %} {% else %} {% trans "Create New Job Posting" %} {% endif %} <i class="fas fa-bullhorn me-2"></i> {% if form.instance.pk %} {% trans "Edit Job Posting" %} {% else %} {% trans "Create New Job Posting" %} {% endif %}
</h1> </h1>
@ -150,8 +150,113 @@
</div> </div>
</div> </div>
{# ================================================= #} {# ================================================= #}
{# SECTION 2: JOB CONTENT (CKEDITOR 5 Fields) #} {# SECTION 2: INTERNAL AND PROMOTION #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-tags"></i> {% trans "Internal & Promotion" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.position_number.id_for_label }}" class="form-label">{% trans "Position Number" %}</label>
{{ form.position_number }}
{% if form.position_number.errors %}<div class="text-danger small mt-1">{{ form.position_number.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">{% trans "Reports To" %}</label>
{{ form.reporting_to }}
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
{{ form.open_positions }}
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
{{ form.max_applications }}
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
</div>
</div>
<div class="col-12">
<div>
<label for="{{ form.hash_tags.id_for_label }}" class="form-label">{% trans "Hashtags (For Promotion/Search on Linkedin)" %}</label>
{{ form.hash_tags }}
{% if form.hash_tags.errors %}<div class="text-danger small mt-1">{{ form.hash_tags.errors }}</div>{% endif %}
<div class="form-text">{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}</div>
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 3: LOCATION AND DATES #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-map-marker-alt"></i> {% trans "Location, Dates, & Salary" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-4">
<div>
<label for="{{ form.location_city.id_for_label }}" class="form-label">{% trans "City" %}</label>
{{ form.location_city }}
{% if form.location_city.errors %}<div class="text-danger small mt-1">{{ form.location_city.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_state.id_for_label }}" class="form-label">{% trans "State/Province" %}</label>
{{ form.location_state }}
{% if form.location_state.errors %}<div class="text-danger small mt-1">{{ form.location_state.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_country.id_for_label }}" class="form-label">{% trans "Country" %}</label>
{{ form.location_country }}
{% if form.location_country.errors %}<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}<span class="text-danger">*</span></label>
{{ form.application_deadline }}
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.salary_range.id_for_label }}" class="form-label">{% trans "Salary Range" %}</label>
{{ form.salary_range }}
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 4: JOB CONTENT (CKEDITOR 5 Fields) #}
{# ================================================= #} {# ================================================= #}
<div class="card mb-4 shadow-sm"> <div class="card mb-4 shadow-sm">
<div class="card-header-themed"> <div class="card-header-themed">
@ -179,21 +284,15 @@
</div> </div>
{# ================================================= #} {# ================================================= #}
{# SECTION 3: COMPENSATION AND APPLICATION #} {# SECTION 5: APPLICATION Instructions #}
{# ================================================= #} {# ================================================= #}
<div class="card mb-4 shadow-sm"> <div class="card mb-4 shadow-sm">
<div class="card-header-themed"> <div class="card-header-themed">
<h5><i class="fas fa-dollar-sign"></i> {% trans "Compensation & Application" %}</h5> <h5><i class="fas fa-dollar-sign"></i> {% trans "Benefits & Application Instructions" %}</h5>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row g-4"> <div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.salary_range.id_for_label }}" class="form-label">{% trans "Salary Range" %}</label>
{{ form.salary_range }}
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
</div>
</div>
{% comment %} (application_url comment removed for brevity) {% endcomment %} {% comment %} (application_url comment removed for brevity) {% endcomment %}
<div class="col-12"> <div class="col-12">
@ -215,117 +314,7 @@
</div> </div>
</div> </div>
{# ================================================= #}
{# SECTION 4: LOCATION AND DATES #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-map-marker-alt"></i> {% trans "Location, Dates, & Status" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-4">
<div>
<label for="{{ form.location_city.id_for_label }}" class="form-label">{% trans "City" %}</label>
{{ form.location_city }}
{% if form.location_city.errors %}<div class="text-danger small mt-1">{{ form.location_city.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_state.id_for_label }}" class="form-label">{% trans "State/Province" %}</label>
{{ form.location_state }}
{% if form.location_state.errors %}<div class="text-danger small mt-1">{{ form.location_state.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_country.id_for_label }}" class="form-label">{% trans "Country" %}</label>
{{ form.location_country }}
{% if form.location_country.errors %}<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}</label>
{{ form.application_deadline }}
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.application_start_date.id_for_label }}" class="form-label">{% trans "Application Start Date" %}</label>
{{ form.application_start_date }}
{% if form.application_start_date.errors %}<div class="text-danger small mt-1">{{ form.application_start_date.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.joining_date.id_for_label }}" class="form-label">{% trans "Desired Joining Date" %}</label>
{{ form.joining_date }}
{% if form.joining_date.errors %}<div class="text-danger small mt-1">{{ form.joining_date.errors }}</div>{% endif %}
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 5: INTERNAL AND PROMOTION #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-tags"></i> {% trans "Internal & Promotion" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.position_number.id_for_label }}" class="form-label">{% trans "Position Number" %}</label>
{{ form.position_number }}
{% if form.position_number.errors %}<div class="text-danger small mt-1">{{ form.position_number.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">{% trans "Reports To" %}</label>
{{ form.reporting_to }}
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
{{ form.open_positions }}
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
{{ form.max_applications }}
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.created_by.id_for_label }}" class="form-label">{% trans "Created By" %}</label>
{{ form.created_by }}
{% if form.created_by.errors %}<div class="text-danger small mt-1">{{ form.created_by.errors }}</div>{% endif %}
</div>
</div>
<div class="col-12">
<div>
<label for="{{ form.hash_tags.id_for_label }}" class="form-label">{% trans "Hashtags (For Promotion/Search)" %}</label>
{{ form.hash_tags }}
{% if form.hash_tags.errors %}<div class="text-danger small mt-1">{{ form.hash_tags.errors }}</div>{% endif %}
<div class="form-text">{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}</div>
</div>
</div>
</div>
</div>
</div>
{# ================================================= #} {# ================================================= #}
{# ACTION BUTTONS #} {# ACTION BUTTONS #}

View File

@ -1,4 +1,3 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load i18n static %} {% load i18n static %}
@ -217,8 +216,17 @@
padding-top: 0.5rem; padding-top: 0.5rem;
} }
/* Custom CSS for simplified stat card (from previous answer) */
.stats-grid .kpi-card {
border-left: 4px solid var(--kaauh-teal);
background-color: #f0faff;
}
.stats-grid .card-body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style> </style>
{% endblock %} {% endblock %}
@ -236,7 +244,7 @@
<div class="row g-4"> <div class="row g-4">
{# LEFT COLUMN: JOB DETAILS WITH TABS #} {# LEFT COLUMN: JOB DETAILS WITH TABS #}
<div class="col-lg-8"> <div class="col-lg-7">
<div class="card shadow-sm no-hover"> <div class="card shadow-sm no-hover">
{# HEADER SECTION #} {# HEADER SECTION #}
@ -248,6 +256,7 @@
<div class="d-flex align-items-center gap-2"> <div class="d-flex align-items-center gap-2">
<span class="badge status-badge"> <span class="badge status-badge">
{# Corrected status badge logic to close the span correctly #}
{% if job.status == "ACTIVE" %} {% if job.status == "ACTIVE" %}
<span class="badge bg-success status-badge"> <span class="badge bg-success status-badge">
{% elif job.status == "DRAFT" %} {% elif job.status == "DRAFT" %}
@ -262,6 +271,7 @@
<span class="badge bg-secondary status-badge"> <span class="badge bg-secondary status-badge">
{% endif %} {% endif %}
{{ job.get_status_display }} {{ job.get_status_display }}
</span>
<button type="button" class="btn btn-outline-light btn-sm ms-2" data-bs-toggle="modal" data-bs-target="#editStatusModal"> <button type="button" class="btn btn-outline-light btn-sm ms-2" data-bs-toggle="modal" data-bs-target="#editStatusModal">
<i class="fas fa-edit text-primary"></i> <i class="fas fa-edit text-primary"></i>
</button> </button>
@ -281,13 +291,12 @@
<i class="fas fa-file-alt me-1"></i> {% trans "Description & Requirements" %} <i class="fas fa-file-alt me-1"></i> {% trans "Description & Requirements" %}
</button> </button>
</li> </li>
{% if job.application_instructions %}
<li class="nav-item" role="presentation"> <li class="nav-item" role="presentation">
<button class="nav-link" id="instructions-tab" data-bs-toggle="tab" data-bs-target="#instructions" type="button" role="tab" aria-controls="instructions" aria-selected="false"> <button class="nav-link" id="kpis-tab" data-bs-toggle="tab" data-bs-target="#kpis" type="button" role="tab" aria-controls="kpis" aria-selected="false">
<i class="fas fa-paper-plane me-1"></i> {% trans "Application" %} <i class="fas fa-chart-line me-1"></i> {% trans "Application KPIs" %}
</button> </button>
</li> </li>
{% endif %}
</ul> </ul>
<div class="card-body"> <div class="card-body">
@ -323,21 +332,20 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<button <button
type="button" type="button"
class="btn btn-main-action btn-sm" class="btn btn-main-action btn-sm"
id="copyJobLinkButton" id="copyJobLinkButton"
data-url="{{ job.application_url }}"> data-url="{{ job.application_url }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 heroicon"> {# Replaced bulky SVG with simpler Font Awesome icon #}
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" /> <i class="fas fa-link"></i>
</svg> {% trans "Share Public Link" %}
{% trans "Share Public Link" %} </button>
</button>
<span id="copyFeedback" class="text-success ms-2 small" style="display:none;"> <span id="copyFeedback" class="text-success ms-2 small" style="display:none;">
{% trans "Copied!" %} {% trans "Copied!" %}
</span> </span>
</div> </div>
</div> </div>
<h5 class="text-muted mb-3">{% trans "Financial & Timeline" %}</h5> <h5 class="text-muted mb-3">{% trans "Financial & Timeline" %}</h5>
@ -392,19 +400,74 @@
<div class="text-secondary">{{ job.benefits|safe}}</div> <div class="text-secondary">{{ job.benefits|safe}}</div>
</div> </div>
{% endif %} {% endif %}
</div> {% if job.application_instructions %}
<div class="mb-4">
{# TAB 3 CONTENT: APPLICATION INSTRUCTIONS #}
{% if job.application_instructions %}
<div class="tab-pane fade" id="instructions" role="tabpanel" aria-labelledby="instructions-tab">
<div class="mb-4">
<h5>{% trans "Application Instructions" %}</h5> <h5>{% trans "Application Instructions" %}</h5>
<div class="text-secondary">{{ job.application_instructions|safe }}</div> <div class="text-secondary">{{ job.application_instructions|safe }}</div>
</div> </div>
{% endif %}
</div>
{# TAB 3 CONTENT: APPLICATION KPIS #}
<div class="tab-pane fade" id="kpis" role="tabpanel" aria-labelledby="kpis-tab">
<div class="row g-3 stats-grid">
{# 1. Job Avg. Score #}
<div class="col-6 col-md-3">
<div class="card text-center h-100 kpi-card">
<div class="card-body p-2">
<i class="fas fa-star text-primary mb-1 d-block" style="font-size: 1.2rem;"></i>
<div class="h4 mb-0 text-primary fw-bold">{{ avg_match_score|floatformat:1 }}</div>
<small class="text-muted d-block">{% trans "Avg. AI Score" %}</small>
</div>
</div>
</div>
{# 2. High Potential Count #}
<div class="col-6 col-md-3">
<div class="card text-center h-100">
<div class="card-body p-2">
<i class="fas fa-trophy text-success mb-1 d-block" style="font-size: 1.2rem;"></i>
<div class="h4 mb-0 text-success fw-bold">{{ high_potential_count }}</div>
<small class="text-muted d-block">{% trans "High Potential" %}</small>
</div>
</div>
</div>
{# 3. Avg. Time to Interview #}
<div class="col-6 col-md-3">
<div class="card text-center h-100">
<div class="card-body p-2">
<i class="fas fa-calendar-alt text-info mb-1 d-block" style="font-size: 1.2rem;"></i>
<div class="h4 mb-0 text-info fw-bold">{{ avg_t2i_days|floatformat:1 }}d</div>
<small class="text-muted d-block">{% trans "Time to Interview" %}</small>
</div>
</div>
</div>
{# 4. Avg. Exam Review Time #}
<div class="col-6 col-md-3">
<div class="card text-center h-100">
<div class="card-body p-2">
<i class="fas fa-hourglass-half text-secondary mb-1 d-block" style="font-size: 1.2rem;"></i>
<div class="h4 mb-0 text-secondary fw-bold">{{ avg_t_in_exam_days|floatformat:1 }}d</div>
<small class="text-muted d-block">{% trans "Avg. Exam Review" %}</small>
</div>
</div>
</div>
</div>
<p class="text-end text-muted small mt-3 me-2">
<i class="fas fa-info-circle me-1"></i> {% trans "KPIs based on completed applicant data." %}
</p>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
{# FOOTER ACTIONS #} {# FOOTER ACTIONS #}
@ -423,7 +486,7 @@
</div> </div>
{# RIGHT COLUMN: TABBED CARDS #} {# RIGHT COLUMN: TABBED CARDS #}
<div class="col-lg-4 "> <div class="col-lg-5">
{# New Card for Candidate Category Chart #} {# New Card for Candidate Category Chart #}
<div class="card shadow-sm no-hover mb-4"> <div class="card shadow-sm no-hover mb-4">
@ -440,44 +503,9 @@
</div> </div>
</div> </div>
<div class="card shadow-sm no-hover mb-4"> {# REMOVED: Standalone Applicant Tracking Card (It is now in a tab) #}
<div class="card-body p-4">
<h6 class="text-muted mb-4">{% trans "Applicant Tracking" %}</h6>
{% include 'jobs/partials/applicant_tracking.html' %}
</div>
</div>
<div class="stats"> <div class="card shadow-sm no-hover">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-star stat-icon"></i> {% trans "Job Avg. Score" %}</h3>
</div>
<div class="stat-value">{{ avg_match_score|floatformat:1 }}</div>
<div class="stat-caption">{% trans "Average AI Match Score (0-100)" %}</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-trophy stat-icon" style="color: var(--color-success);"></i> {% trans "High Potential Count" %}</h3>
</div>
<div class="stat-value">{{ high_potential_count }}</div>
<div class="stat-caption">{% trans "Candidates with Score ≥ 75%" %}</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-calendar-alt stat-icon" style="color: var(--kaauh-teal-light);"></i> {% trans "Avg. Time to Interview" %}</h3>
</div>
<div class="stat-value">{{ avg_t2i_days|floatformat:1 }}d</div>
<div class="stat-caption">{% trans "Applied to Interview (Total Funnel Speed)" %}</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-hourglass-half stat-icon" style="color: var(--color-info);"></i> {% trans "Avg. Exam Review Time" %}</h3>
</div>
<div class="stat-value">{{ avg_t_in_exam_days|floatformat:1 }}d</div>
<div class="stat-caption">{% trans "Days spent between Exam and Interview" %}</div>
</div>
</div>
<div class="card shadow-sm no-hover" style="height:350px;">
{# RIGHT TABS NAVIGATION #} {# RIGHT TABS NAVIGATION #}
<ul class="nav nav-tabs right-column-tabs" id="rightJobTabs" role="tablist"> <ul class="nav nav-tabs right-column-tabs" id="rightJobTabs" role="tablist">
@ -486,14 +514,19 @@
<i class="fas fa-users me-1 text-primary"></i> {% trans "Applicants" %} <i class="fas fa-users me-1 text-primary"></i> {% trans "Applicants" %}
</button> </button>
</li> </li>
<li class="nav-item flex-fill" role="presentation">
<button class="nav-link" id="tracking-tab" data-bs-toggle="tab" data-bs-target="#tracking-pane" type="button" role="tab" aria-controls="tracking-pane" aria-selected="false">
<i class="fas fa-project-diagram me-1 text-primary"></i> {% trans "Tracking" %}
</button>
</li>
<li class="nav-item flex-fill" role="presentation"> <li class="nav-item flex-fill" role="presentation">
<button class="nav-link" id="manage-tab" data-bs-toggle="tab" data-bs-target="#manage-pane" type="button" role="tab" aria-controls="manage-pane" aria-selected="false"> <button class="nav-link" id="manage-tab" data-bs-toggle="tab" data-bs-target="#manage-pane" type="button" role="tab" aria-controls="manage-pane" aria-selected="false">
<i class="fas fa-cogs me-1 text-secondary"></i> {% trans "Form Template" %} <i class="fas fa-cogs me-1 text-secondary"></i> {% trans "Form Template" %}
</button> </button>
</li> </li>
<li class="nav-item flex-fill" role="presentation"> <li class="nav-item flex-fill" role="presentation">
<button class="nav-link" id="internal-tab" data-bs-toggle="tab" data-bs-target="#internal-pane" type="button" role="tab" aria-controls="internal-pane" aria-selected="false"> <button class="nav-link" id="linkedin-tab" data-bs-toggle="tab" data-bs-target="#linkedin-pane" type="button" role="tab" aria-controls="linkedin-pane" aria-selected="false">
<i class="fas fa-info me-1 text-muted"></i> {% trans "Linkedin" %} <i class="fab fa-linkedin me-1 text-info"></i> {% trans "LinkedIn" %}
</button> </button>
</li> </li>
</ul> </ul>
@ -503,34 +536,6 @@
{# TAB 1: APPLICANTS CONTENT #} {# TAB 1: APPLICANTS CONTENT #}
<div class="tab-pane fade show active" id="applicants-pane" role="tabpanel" aria-labelledby="applicants-tab"> <div class="tab-pane fade show active" id="applicants-pane" role="tabpanel" aria-labelledby="applicants-tab">
<h5 class="mb-3">{% trans "Total Applicants" %} (<span id="total_candidates">{{ total_applicants }}</span>)</h5> <h5 class="mb-3">{% trans "Total Applicants" %} (<span id="total_candidates">{{ total_applicants }}</span>)</h5>
{% comment %} {% if total_applicants > 0 %}
<div class="row mb-4 applicant-stats">
<div class="col-4">
<div class="stat-item">
<div class="text-primary">{{ applied_count }}</div>
<small class="text-muted">{% trans "Applied" %}</small>
</div>
</div>
<div class="col-4">
<div class="stat-item">
<div class="text-info">{{ interview_count }}</div>
<small class="text-muted">{% trans "Interview" %}</small>
</div>
</div>
<div class="col-4">
<div class="stat-item">
<div class="text-success">{{ offer_count }}</div>
<small class="text-muted">{% trans "Offer" %}</small>
</div>
</div>
</div>
<div class="col-12 mb-2">
<a href="{% url 'job_candidates_list' job.slug %}" class="btn btn-outline-secondary w-100">
{% trans "View All Applicants" %} ({{ total_applicants }})
</a>
</div>
{% endif %} {% endcomment %}
<div class="d-grid gap-4"> <div class="d-grid gap-4">
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action"> <a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action">
@ -542,13 +547,16 @@
</div> </div>
</div> </div>
{# TAB 2: MANAGEMENT (LinkedIn & Forms) CONTENT #} {# NEW TAB 2: APPLICANT TRACKING CONTENT #}
<div class="tab-pane fade" id="tracking-pane" role="tabpanel" aria-labelledby="tracking-tab">
<h5 class="mb-3"><i class="fas fa-project-diagram me-2 text-primary"></i>{% trans "Pipeline Stages" %}</h5>
{% include 'jobs/partials/applicant_tracking.html' %}
<p class="text-muted small mt-3">{% trans "View the number of candidates currently in each stage of the hiring pipeline." %}</p>
</div>
{# TAB 3: MANAGEMENT (Form Template) CONTENT #}
<div class="tab-pane fade" id="manage-pane" role="tabpanel" aria-labelledby="manage-tab"> <div class="tab-pane fade" id="manage-pane" role="tabpanel" aria-labelledby="manage-tab">
{# LinkedIn Integration (Content from old card) #}
{# Applicant Form Management (Content from old card) #}
<h5 class="mb-3"><i class="fas fa-clipboard-list me-2 text-primary"></i>{% trans "Form Management" %}</h5> <h5 class="mb-3"><i class="fas fa-clipboard-list me-2 text-primary"></i>{% trans "Form Management" %}</h5>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<p class="text-muted small mb-3"> <p class="text-muted small mb-3">
@ -569,8 +577,8 @@
</div> </div>
</div> </div>
{# TAB 3: INTERNAL INFO CONTENT #} {# TAB 4: LINKEDIN INTEGRATION CONTENT #}
<div class="tab-pane fade" id="internal-pane" role="tabpanel" aria-labelledby="internal-tab"> <div class="tab-pane fade" id="linkedin-pane" role="tabpanel" aria-labelledby="linkedin-tab">
<h5 class="mb-3"><i class="fab fa-linkedin me-2 text-info"></i>{% trans "LinkedIn Integration" %}</h5> <h5 class="mb-3"><i class="fab fa-linkedin me-2 text-info"></i>{% trans "LinkedIn Integration" %}</h5>
<div class="mb-4"> <div class="mb-4">
{% if job.posted_to_linkedin %} {% if job.posted_to_linkedin %}
@ -611,48 +619,6 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% comment %} {# Applicant Form Management (Content from old card) #}
<h5 class="mb-3"><i class="fas fa-clipboard-list me-2 text-primary"></i>{% trans "Form Management" %}</h5> {% endcomment %}
{% comment %} <div class="d-grid gap-2">
<p class="text-muted small mb-3">
{% trans "Manage the custom application forms associated with this job posting." %}
</p> {% endcomment %}
{% comment %} <a href="{% url 'create_form_template' %}" class="btn btn-main-action">
<i class="fas fa-plus-circle me-2"></i> {% trans "Create New Form" %}
</a>
<a href="" class="btn btn-outline-secondary">
<i class="fas fa-list-alt me-1"></i> {% trans "View All Existing Forms" %}
</a> {% endcomment %}
{% comment %} <a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action">
<i class="fas fa-user-plus"></i> {% trans "Create Candidate" %}
</a>
<a href="{% url 'candidate_screening_view' job.slug %}" class="btn btn-main-action">
<i class="fas fa-layer-group"></i> {% trans "Manage Tiers" %}
</a> {% endcomment %}
</div>
</div>
{# TAB 3: INTERNAL INFO CONTENT #}
<div class="tab-pane fade" id="internal-pane" role="tabpanel" aria-labelledby="internal-tab">
<h5 class="mb-3"><i class="fas fa-info-circle me-2 text-secondary"></i>{% trans "Internal Information" %}</h5>
<div class="small">
<p class="mb-1"><strong>{% trans "Internal Job ID:" %}</strong> {{ job.internal_job_id }}</p>
<p class="mb-1"><strong>{% trans "Created:" %}</strong> {{ job.created_at|date:"M d, Y" }}</p>
<p class="mb-1"><strong>{% trans "Last Updated:" %}</strong> {{ job.updated_at|date:"M d, Y" }}</p>
{% if job.reporting_to %}
<p class="mb-0"><strong>{% trans "Reports To:" %}</strong> {{ job.reporting_to }}</p>
{% endif %}
</div>
<div class="mt-4">
<a href="{% url 'job_list' %}" class="btn btn-outline-secondary w-100">
<i class="fas fa-arrow-left"></i> {% trans "Back to Jobs" %}
</a>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -660,10 +626,8 @@
</div> </div>
</div> </div>
</div> </div>
<!--image modal class-->
{% include "jobs/partials/image_upload.html" %} {% include "jobs/partials/image_upload.html" %}
<!-- JOB STATUS MODAL-->
<div class="modal fade" id="editStatusModal" tabindex="-1" aria-labelledby="editStatusModalLabel" aria-hidden="true"> <div class="modal fade" id="editStatusModal" tabindex="-1" aria-labelledby="editStatusModalLabel" aria-hidden="true">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@ -811,4 +775,4 @@
}); });
</script> </script>
{% endblock %} {% endblock %}

View File

@ -278,7 +278,7 @@
</tr> </tr>
<tr class="nested-metrics-row"> <tr class="nested-metrics-row">
<th style="width: calc(50% / 7);">{% trans "All Applicants" %}</th> <th style="width: calc(50% / 7);">{% trans "All" %}</th>
<th style="width: calc(50% / 7);">{% trans "Screened" %}</th> <th style="width: calc(50% / 7);">{% trans "Screened" %}</th>
<th style="width: calc(50% / 7 * 2);">{% trans "Exam" %}</th> <th style="width: calc(50% / 7 * 2);">{% trans "Exam" %}</th>
<th style="width: calc(50% / 7 * 2);">{% trans "Interview" %}</th> <th style="width: calc(50% / 7 * 2);">{% trans "Interview" %}</th>
@ -386,7 +386,7 @@
{# --- END OF JOB LIST CONTAINER --- #} {# --- END OF JOB LIST CONTAINER --- #}
{% include "includes/paginator.html" %} {% include "includes/paginator.html" %}
{% if not jobs and not job_list_data and not page_obj %} {% if not jobs and not job_list_data and not page_obj %}
<div class="text-center py-5 card shadow-sm"> <div class="text-center py-5 card shadow-sm mt-4">
<div class="card-body"> <div class="card-body">
<i class="fas fa-briefcase fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i> <i class="fas fa-briefcase fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
<h3>{% trans "No job postings found" %}</h3> <h3>{% trans "No job postings found" %}</h3>

View File

@ -1,10 +1,9 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static i18n %} {% load static i18n %}
{% block customCSS %} {% block customCSS %}
<style> <style>
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
/* KAAT-S Redesign CSS */ /* KAAT-S Redesign CSS - Optimized Compact Detail View (Settings Removed) */
/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */
:root { :root {
@ -20,93 +19,56 @@
} }
body { body {
background-color: #f0f2f5; /* Off-white page background */ background-color: #f0f2f5;
font-family: 'Inter', sans-serif; /* Use a modern font stack */ font-family: 'Inter', sans-serif;
} }
/* ------------------ General Layout & Card Styles ------------------ */ /* ------------------ General Layout & Card Styles ------------------ */
.container {
width:auto;
padding: 3rem 1.5rem;
}
.card { .card {
border: none; /* Remove default border */ border: none;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.08), 0 4px 10px rgba(0,0,0,0.05); /* Deep, soft shadow */ box-shadow: 0 5px 15px rgba(0,0,0,0.05), 0 2px 5px rgba(0,0,0,0.03);
background-color: white; margin-bottom: 1.5rem;
margin-bottom: 2.5rem; transition: all 0.2s ease;
transition: all 0.3s ease;
} }
.card:not(.no-hover):hover { .card:not(.no-hover):hover {
transform: translateY(-3px); transform: translateY(-2px);
box-shadow: 0 15px 40px rgba(0,0,0,0.1), 0 6px 15px rgba(0,0,0,0.08); box-shadow: 0 10px 20px rgba(0,0,0,0.08);
}
.card.no-hover:hover {
transform: none;
box-shadow: 0 10px 30px rgba(0,0,0,0.08), 0 4px 10px rgba(0,0,0,0.05);
} }
.card-body { .card-body {
padding: 2rem; padding: 1.5rem;
} }
/* ------------------ Header & Title Styles ------------------ */ /* ------------------ Main Header & Title Styles ------------------ */
.card-header { .main-title-card {
background-color: var(--kaauh-gray-light); padding: 1.5rem 2rem;
border-bottom: 1px solid var(--kaauh-border); background-color: white;
padding: 2rem; border-bottom: 3px solid var(--kaauh-teal);
border-radius: 12px 12px 0 0; border-radius: 12px 12px 0 0;
display: flex;
justify-content: space-between;
align-items: flex-start; /* Align title group to the top */
flex-wrap: wrap;
}
.card-header-title-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.card-header h1 {
color: var(--kaauh-teal-dark);
font-weight: 800; /* Extra bold for prominence */
margin: 0;
display: flex;
align-items: center;
gap: 1rem;
font-size: 2.5rem;
}
.card-header .heroicon {
width: 2.5rem;
height: 2.5rem;
color: var(--kaauh-teal);
}
.card-header .btn-secondary-back {
/* Subtle Back Button */
align-self: flex-start;
background-color: transparent;
border: none;
color: var(--kaauh-secondary-text);
font-weight: 600;
font-size: 1rem;
padding: 0.5rem 0.75rem;
transition: color 0.2s;
}
.card-header .btn-secondary-back:hover {
color: var(--kaauh-teal);
text-decoration: underline;
} }
/* ------------------ Status Badge Styles ------------------ */ .main-title-card h1 {
color: var(--kaauh-teal-dark);
font-weight: 800;
margin: 0;
font-size: 2rem;
}
.main-title-card .heroicon {
width: 2rem;
height: 2rem;
color: var(--kaauh-teal);
}
.status-badge { .status-badge {
font-size: 0.85rem; font-size: 0.75rem;
padding: 0.5em 1em; padding: 0.35em 0.8em;
border-radius: 20px; /* Pill shape */ border-radius: 15px;
font-weight: 700; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.5px; letter-spacing: 0.5px;
margin-top: 0.5rem;
} }
.bg-waiting { background-color: #ffc107 !important; color: var(--kaauh-primary-text) !important;} .bg-waiting { background-color: #ffc107 !important; color: var(--kaauh-primary-text) !important;}
.bg-started { background-color: var(--kaauh-teal) !important; color: white !important;} .bg-started { background-color: var(--kaauh-teal) !important; color: white !important;}
@ -114,13 +76,13 @@ body {
/* ------------------ Detail Row & Content Styles ------------------ */ /* ------------------ Detail Row & Content Styles ------------------ */
.card h2 { .detail-section h2 {
color: var(--kaauh-teal-dark); color: var(--kaauh-teal-dark);
font-weight: 700; font-weight: 700;
padding: 1.5rem 2rem 1rem; font-size: 1.25rem;
margin: 0; margin-bottom: 1rem;
font-size: 1.5rem; border-bottom: 2px solid var(--kaauh-teal-light);
border-bottom: 1px solid var(--kaauh-border); padding-bottom: 0.5rem;
} }
.detail-row-group { .detail-row-group {
@ -128,10 +90,10 @@ body {
} }
.detail-row { .detail-row {
display: grid; display: flex;
grid-template-columns: minmax(150px, 40%) 1fr; justify-content: space-between;
padding: 1rem 2rem; padding: 0.75rem 0;
border-bottom: 1px solid var(--kaauh-border); border-bottom: 1px dashed var(--kaauh-border);
align-items: center; align-items: center;
} }
.detail-row:last-child { .detail-row:last-child {
@ -139,273 +101,194 @@ body {
} }
.detail-label { .detail-label {
font-weight: 600; font-weight: 600;
color: var(--kaauh-teal-dark); color: var(--kaauh-teal);
text-align: left; font-size: 0.9rem;
font-size: 0.95rem; flex-basis: 45%;
} }
.detail-value { .detail-value {
text-align: right;
color: var(--kaauh-primary-text); color: var(--kaauh-primary-text);
word-wrap: break-word; word-wrap: break-word;
font-weight: 500; font-weight: 500;
text-align: right;
font-size: 0.9rem;
flex-basis: 55%;
} }
/* ------------------ Join Info & Copy Button ------------------ */ /* ------------------ Join Info & Copy Button ------------------ */
.join-info-card .card-body { .join-info-card {
padding-top: 2rem; border-left: 5px solid var(--kaauh-teal); /* Highlight join info */
} }
.btn-primary { .btn-primary {
background-color: var(--kaauh-teal); background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal); border-color: var(--kaauh-teal);
color: white;
font-weight: 600; font-weight: 600;
padding: 0.75rem 1.5rem; padding: 0.6rem 1.25rem;
border-radius: 8px; border-radius: 6px;
transition: all 0.2s ease;
} }
.btn-primary:hover { .btn-primary:hover {
background-color: var(--kaauh-teal-dark); background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark); border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0, 99, 110, 0.3);
} }
.join-url-container { .join-url-container {
display: flex; margin-top: 1rem;
gap: 1rem;
align-items: center;
margin-top: 1.5rem;
position: relative;
padding: 1rem 0; /* Add padding for clear space around the copy area */
} }
.join-url-display { .join-url-display {
flex-grow: 1;
background-color: var(--kaauh-gray-light); background-color: var(--kaauh-gray-light);
border: 1px solid var(--kaauh-border); border: 1px solid var(--kaauh-border);
border-radius: 8px; padding: 0.5rem 0.75rem;
padding: 0.75rem 1rem; font-size: 0.85rem;
word-break: break-all;
font-size: 0.9rem;
color: var(--kaauh-secondary-text);
font-family: monospace; /* Monospace for links/code */
} }
.join-url-display strong {
color: var(--kaauh-teal-dark);
font-family: 'Inter', sans-serif;
}
.btn-copy { .btn-copy {
flex-shrink: 0; padding: 0.5rem 0.75rem;
background-color: var(--kaauh-teal-dark); /* Darker teal for a clean utility look */ background-color: var(--kaauh-teal-dark);
border: none;
color: white;
padding: 0.75rem 1rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-copy:hover {
background-color: var(--kaauh-teal);
}
.btn-copy i {
margin-right: 0.25rem;
}
/* 🎯 Copy Message Pill Style */
#copy-message {
position: absolute;
top: -5px;
right: 0;
background-color: var(--kaauh-success);
color: white;
padding: 0.2rem 0.6rem;
border-radius: 20px; /* Pill shape */
opacity: 0;
transition: opacity 0.4s ease-in-out;
z-index: 10;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
} }
/* ------------------ Footer & Actions ------------------ */ /* ------------------ Footer & Actions ------------------ */
.card-footer { .action-bar-footer {
border-top: 1px solid var(--kaauh-border); border-top: 1px solid var(--kaauh-border);
padding: 1.5rem 2rem; padding: 1rem 1.5rem;
display: flex; gap: 0.75rem;
flex-wrap: wrap;
gap: 1rem;
justify-content: flex-start;
background-color: var(--kaauh-gray-light); background-color: var(--kaauh-gray-light);
border-radius: 0 0 12px 12px; border-radius: 0 0 12px 12px;
} }
.btn-danger { .btn-footer-action {
background-color: var(--kaauh-danger);
border-color: var(--kaauh-danger);
color: white;
font-weight: 600; font-weight: 600;
padding: 0.75rem 1.5rem; padding: 0.5rem 1rem;
border-radius: 8px; border-radius: 6px;
transition: all 0.2s ease; font-size: 0.9rem;
}
.btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
} }
.btn-secondary { /* ------------------ Comments Section ------------------ */
background-color: #6c757d; #comments-card .card-header {
border-color: #6c757d; background-color: var(--kaauh-teal-light);
color: white;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #545b62;
}
/* ------------------ API Response Styling ------------------ */
#gateway-response-card {
border-left: 5px solid var(--kaauh-teal); /* Prominent left border */
}
#gateway-response-card .card-body {
padding: 1.5rem;
}
#gateway-response-card h3 {
color: var(--kaauh-teal-dark); color: var(--kaauh-teal-dark);
font-weight: 700; padding: 1rem 1.5rem;
font-size: 1.35rem; font-weight: 600;
margin-bottom: 1rem; border-radius: 12px 12px 0 0;
}
#gateway-response-card pre {
background-color: #fff;
border: 1px solid var(--kaauh-border);
border-radius: 8px;
padding: 1rem;
font-size: 0.8rem;
color: var(--kaauh-primary-text);
white-space: pre-wrap;
word-wrap: break-word;
} }
/* Comment card body/item styling is kept compact */
</style> </style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="container"> <div class="container-fluid">
<div class="card no-hover"> {# --- TOP BAR / BACK BUTTON --- #}
<div class="card-header"> <div class="d-flex justify-content-between align-items-center mb-3">
<div class="card-header-title-group"> <a href="{% url 'list_meetings' %}" class="btn btn-secondary-back">
<h1> <i class="fas fa-arrow-left me-1"></i> {% trans "Back to Meetings" %}
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)"> </a>
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </div>
<path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0 8.268-2.943-9.542-7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg> <div class="row g-4">
{{ meeting.topic }} {# --- LEFT COLUMN (MAIN DETAILS) --- #}
</h1> <div class="col-lg-6">
<div class="col-auto"> <div class="card no-hover h-100">
<span class="status-badge bg-{{ meeting.status }}"> {# --- CONSOLIDATED HEADER --- #}
{{ meeting.status|title }} <div class="main-title-card">
</span> <div class="d-flex justify-content-between align-items-start">
<div class="card-header-title-group">
<h1 class="mb-1">
<svg class="heroicon me-2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0 8.268-2.943-9.542-7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{{ meeting.topic }}
</h1>
<div class="d-flex align-items-center gap-3">
<span class="status-badge bg-{{ meeting.status }}">
{{ meeting.status|title }}
</span>
{% if meeting.interview %}
<span class="text-muted small">
{% trans "Candidate" %}: <a class="text-primary-theme fw-bold text-decoration-none" href="{% url 'candidate_detail' meeting.interview.candidate.slug %}">{{ meeting.interview.candidate.name }} </a>
</span>
{% endif %}
</div>
</div>
</div>
</div> </div>
{% if meeting.interview %}
<div class="col-auto"> {# --- MAIN DETAIL BODY --- #}
<span class="status-badge"> <div class="card-body detail-section">
Candidate Name : <a class="text-primary-theme" href="{% url 'candidate_detail' meeting.interview.candidate.slug %}">{{ meeting.interview.candidate.name }} </a> <h2>{% trans "Core Details" %}</h2>
</span> <div class="detail-row-group">
<div class="detail-row"><div class="detail-label">{% trans "Meeting ID" %}:</div><div class="detail-value">{{ meeting.meeting_id }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Start Time" %}:</div><div class="detail-value">{{ meeting.start_time|date:"M d, Y H:i" }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Duration" %}:</div><div class="detail-value">{{ meeting.duration }} {% trans "minutes" %}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Timezone" %}:</div><div class="detail-value">{{ meeting.timezone|default:"UTC" }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Host Email" %}:</div><div class="detail-value">{{ meeting.host_email|default:"N/A" }}</div></div>
</div> </div>
{% endif %} </div>
</div>
<a href="{% url 'list_meetings' %}" class="btn btn-secondary-back">
<i class="fas fa-arrow-left"></i> {% trans "Back to Meetings" %}
</a>
</div>
</div>
<div class="card no-hover"> {# --- ACTION BAR AT THE BOTTOM OF THE MAIN CARD --- #}
<h2>{% trans "Meeting Information" %}</h2> <div class="card-footer action-bar-footer d-flex justify-content-end">
<div class="card-body detail-row-group"> <a href="{% url 'update_meeting' meeting.slug %}" class="btn btn-primary btn-footer-action">
<div class="detail-row"><div class="detail-label">{% trans "Meeting ID" %}:</div><div class="detail-value">{{ meeting.meeting_id }}</div></div> <i class="fas fa-edit me-1"></i> {% trans "Update" %}
<div class="detail-row"><div class="detail-label">{% trans "Topic" %}:</div><div class="detail-value">{{ meeting.topic }}</div></div> </a>
<div class="detail-row"><div class="detail-label">{% trans "Start Time" %}:</div><div class="detail-value">{{ meeting.start_time|date:"M d, Y H:i" }}</div></div> {% if meeting.zoom_gateway_response %}
<div class="detail-row"><div class="detail-label">{% trans "Duration" %}:</div><div class="detail-value">{{ meeting.duration }} {% trans "minutes" %}</div></div> <button type="button" class="btn btn-secondary btn-footer-action" onclick="toggleGateway()">
<div class="detail-row"><div class="detail-label">{% trans "Timezone" %}:</div><div class="detail-value">{{ meeting.timezone|default:"UTC" }}</div></div> <i class="fas fa-code me-1"></i> {% trans "API Response" %}
<div class="detail-row"><div class="detail-label">{% trans "Host Email" %}:</div><div class="detail-value">{{ meeting.host_email|default:"N/A" }}</div></div> </button>
</div> {% endif %}
</div> <button type="button" class="btn btn-danger btn-footer-action" title="{% trans 'Delete' %}"
{% if meeting.join_url %} data-bs-toggle="modal" data-bs-target="#deleteModal"
<div class="card no-hover join-info-card"> hx-post="{% url 'delete_meeting' meeting.slug %}"
<h2>{% trans "Join Information" %}</h2> hx-target="#deleteModalBody"
<div class="card-body"> hx-swap="outerHTML"
<a href="{{ meeting.join_url }}" class="btn btn-primary" target="_blank"> data-item-name="{{ meeting.topic }}">
<i class="fas fa-video"></i> {% trans "Join Meeting Now" %} <i class="fas fa-trash-alt me-1"></i>
</a> Delete
<div class="join-url-container">
<div id="copy-message" style="opacity: 0;">{% trans "Copied!" %}</div>
<div class="join-url-display" id="join-url-display">
<strong>{% trans "Join URL" %}:</strong> <span id="meeting-join-url">{{ meeting.join_url }}</span>
</div>
<button class="btn-copy" onclick="copyLink()">
<i class="fas fa-copy"></i>
</button> </button>
</div> </div>
{% if meeting.password %}
<div class="detail-row" style="border: none; padding: 1rem 0 0 0;">
<div class="detail-label" style="font-size: 1rem;">{% trans "Password" %}:</div>
<div class="detail-value" style="font-weight: 700; color: var(--kaauh-danger);">{{ meeting.password }}</div>
</div>
{% endif %}
</div> </div>
</div> </div>
{% endif %}
<div class="card no-hover"> {# --- RIGHT COLUMN (JOIN INFO) --- #}
<h2>{% trans "Settings" %}</h2> <div class="col-lg-6">
<div class="card-body detail-row-group"> {% if meeting.join_url %}
<div class="detail-row"><div class="detail-label">{% trans "Participant Video" %}:</div><div class="detail-value">{{ meeting.participant_video|yesno:"Yes,No" }}</div></div> <div class="card no-hover join-info-card detail-section h-100">
<div class="detail-row"><div class="detail-label">{% trans "Join Before Host" %}:</div><div class="detail-value">{{ meeting.join_before_host|yesno:"Yes,No" }}</div></div> <div class="card-body">
<div class="detail-row"><div class="detail-label">{% trans "Mute Upon Entry" %}:</div><div class="detail-value">{{ meeting.mute_upon_entry|yesno:"Yes,No" }}</div></div> <h2>{% trans "Join Information" %}</h2>
<div class="detail-row"><div class="detail-label">{% trans "Waiting Room" %}:</div><div class="detail-value">{{ meeting.waiting_room|yesno:"Yes,No" }}</div></div>
</div>
</div>
<div class="card no-hover"> <a href="{{ meeting.join_url }}" class="btn btn-primary w-100 mb-4" target="_blank">
<div class="card-footer"> <i class="fas fa-video me-1"></i> {% trans "Join Meeting Now" %}
<a href="{% url 'update_meeting' meeting.slug %}" class="btn btn-primary"> </a>
<i class="fas fa-edit"></i> {% trans "Update Meeting" %}
</a>
{% if meeting.zoom_gateway_response %} <div class="join-url-container">
<button type="button" class="btn btn-secondary" onclick="toggleGateway()"> <div id="copy-message" style="opacity: 0;">{% trans "Copied!" %}</div>
<i class="fas fa-code"></i> {% trans "View API Response" %}
</button> <div class="join-url-display d-flex justify-content-between align-items-center">
<div class="text-truncate">
<strong>{% trans "Join URL" %}:</strong>
<span id="meeting-join-url">{{ meeting.join_url }}</span>
</div>
<button class="btn-copy ms-2" onclick="copyLink()" title="{% trans 'Copy URL' %}">
<i class="fas fa-copy"></i>
</button>
</div>
</div>
{% if meeting.password %}
<div class="detail-row" style="border: none; padding-top: 1rem;">
<div class="detail-label" style="font-size: 1rem;">{% trans "Password" %}:</div>
<div class="detail-value fw-bolder text-danger">{{ meeting.password }}</div>
</div>
{% endif %}
</div>
</div>
{% endif %} {% endif %}
<button type="button" class="btn btn-danger" title="{% trans 'Delete' %}"
data-bs-toggle="modal" data-bs-target="#deleteModal"
hx-post="{% url 'delete_meeting' meeting.slug %}"
hx-target="#deleteModalBody"
hx-swap="outerHTML"
data-item-name="{{ meeting.topic }}">
<i class="fas fa-trash-alt"></i>
Delete Meeting
</button>
</div> </div>
</div> </div>
{# --- API RESPONSE CARD (Full width, hidden by default) --- #}
{% if meeting.zoom_gateway_response %} {% if meeting.zoom_gateway_response %}
<div id="gateway-response-card" class="card" style="display: none;"> <div id="gateway-response-card" class="card mt-4" style="display: none;">
<div class="card-body"> <div class="card-body">
<h3>{% trans "API Gateway Response" %}</h3> <h3>{% trans "API Gateway Response" %}</h3>
<pre>{{ meeting.zoom_gateway_response|safe }}</pre> <pre>{{ meeting.zoom_gateway_response|safe }}</pre>
@ -413,72 +296,68 @@ body {
</div> </div>
{% endif %} {% endif %}
<!-- Comments Section -->
<div class="card no-hover" id="comments-card"> {# --- Comments Section (Full Width, below main content) --- #}
<div class="card-header text-primary-theme d-flex justify-content-between align-items-center"> <div class="card no-hover mt-4" id="comments-card">
<h5 class="card-title mb-0"> <div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0" style="color: var(--kaauh-teal-dark);">
<i class="fas fa-comments me-2"></i> <i class="fas fa-comments me-2"></i>
Comments ({{ meeting.comments.count }}) {% trans "Comments" %} ({{ meeting.comments.count }})
</h5> </h5>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<button type="button" class="btn btn-primary btn-sm" <button type="button" class="btn btn-primary btn-sm"
hx-get="{% url 'add_meeting_comment' meeting.slug %}" hx-get="{% url 'add_meeting_comment' meeting.slug %}"
hx-target="#comment-section" hx-target="#comment-section"
> >
<i class="fas fa-plus"></i> Add Comment <i class="fas fa-plus me-1"></i> {% trans "Add Comment" %}
</button> </button>
{% endif %} {% endif %}
</div> </div>
<div class="card-body"> <div class="card-body">
<div id="comment-section"> <div id="comment-section">
{% if meeting.comments.all %} {% if meeting.comments.all %}
<div class="row"> {% for comment in meeting.comments.all|dictsortreversed:"created_at" %}
{% for comment in meeting.comments.all|dictsortreversed:"created_at" %} <div class="card mb-3">
<div class="col-12 mb-3"> <div class="card-header d-flex justify-content-between align-items-center">
<div class="card "> <div>
<div class="card-header d-flex justify-content-between align-items-start"> <strong class="me-2">{{ comment.author.get_full_name|default:comment.author.username }}</strong>
<div> {% if comment.author != user %}
<strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong> <span class="badge bg-secondary ms-1">{% trans "Comment" %}</span>
{% if comment.author != user %} {% endif %}
<span class="badge bg-secondary ms-2">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="fas fa-edit"></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="fas fa-trash"></i>
</button>
</div>
{% endif %}
</div>
</div> </div>
<small class="text-muted">{{ comment.created_at|date:"M d, Y P" }}</small>
</div> </div>
{% endfor %} <div class="card-body">
</div> <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="{% trans 'Edit Comment' %}">
<i class="fas fa-edit"></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="{% trans 'Delete Comment' %}">
<i class="fas fa-trash"></i>
</button>
</div>
{% endif %}
</div>
</div>
{% endfor %}
{% else %} {% else %}
<p class="text-muted">No comments yet. Be the first to comment!</p> <p class="text-muted">{% trans "No comments yet. Be the first to comment!" %}</p>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Comment Modal (for Add/Edit) -->
<div class="modal fade" id="commentModal" tabindex="-1" aria-labelledby="commentModalLabel" aria-hidden="true"> <div class="modal fade" id="commentModal" tabindex="-1" aria-labelledby="commentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">
<div class="modal-content"> <div class="modal-content">
@ -487,8 +366,7 @@ body {
<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="commentModalBody"> <div class="modal-body" id="commentModalBody">
<!-- HTMX will load the form here --> </div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -505,41 +383,36 @@ body {
} }
} }
// CopyLink function remains the same (as provided in the original code)
function copyLink() { function copyLink() {
const urlElement = document.getElementById('meeting-join-url'); const urlElement = document.getElementById('meeting-join-url');
const messageElement = document.getElementById('copy-message'); const messageElement = document.getElementById('copy-message');
const textToCopy = urlElement.textContent || urlElement.innerText; const textToCopy = urlElement.textContent || urlElement.innerText;
// Clear any existing message
clearTimeout(window.copyMessageTimeout); clearTimeout(window.copyMessageTimeout);
// Function to show the message
function showMessage(success) { function showMessage(success) {
messageElement.textContent = success ? '{% trans "Copied!" %}' : '{% trans "Copy Failed." %}'; messageElement.textContent = success ? '{% trans "Copied!" %}' : '{% trans "Copy Failed." %}';
messageElement.style.backgroundColor = success ? 'var(--kaauh-success)' : 'var(--kaauh-danger)'; messageElement.style.backgroundColor = success ? 'var(--kaauh-success)' : 'var(--kaauh-danger)';
messageElement.style.opacity = '1'; messageElement.style.opacity = '1';
// Hide the message after 2 seconds
window.copyMessageTimeout = setTimeout(() => { window.copyMessageTimeout = setTimeout(() => {
messageElement.style.opacity = '0'; messageElement.style.opacity = '0';
}, 2000); }, 2000);
} }
// Use the modern clipboard API
if (navigator.clipboard && window.isSecureContext) { if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(textToCopy).then(() => { navigator.clipboard.writeText(textToCopy).then(() => {
showMessage(true); // Show success message showMessage(true);
}).catch(err => { }).catch(err => {
console.error('Could not copy text: ', err); console.error('Could not copy text: ', err);
fallbackCopyTextToClipboard(textToCopy, showMessage); // Try fallback on failure fallbackCopyTextToClipboard(textToCopy, showMessage);
}); });
} else { } else {
// Fallback for older browsers
fallbackCopyTextToClipboard(textToCopy, showMessage); fallbackCopyTextToClipboard(textToCopy, showMessage);
} }
} }
// Fallback function for older browsers
function fallbackCopyTextToClipboard(text, callback) { function fallbackCopyTextToClipboard(text, callback) {
const textArea = document.createElement("textarea"); const textArea = document.createElement("textarea");
textArea.value = text; textArea.value = text;
@ -560,7 +433,7 @@ body {
} }
document.body.removeChild(textArea); document.body.removeChild(textArea);
callback(success); // Call the message function with the result callback(success);
} }
</script> </script>
{% endblock %} {% endblock %}

View File

@ -294,15 +294,15 @@
<div class="tab-pane fade" id="resume-pane" role="tabpanel" aria-labelledby="resume-tab"> <div class="tab-pane fade" id="resume-pane" role="tabpanel" aria-labelledby="resume-tab">
<h5 class="text-primary mb-4">{% trans "Resume Document" %}</h5> <h5 class="text-primary mb-4">{% trans "Resume Document" %}</h5>
<div class="d-flex align-items-center justify-content-between p-3 border rounded"> <div class="d-flex align-items-center justify-content-between p-3 border rounded">
<div> {% comment %} <div>
<p class="mb-1"><strong>{{ candidate.resume.name }}</strong></p> <p class="mb-1"><strong>{{ candidate.resume.name }}</strong></p>
<small class="text-muted">{{ candidate.resume.name|truncatechars:30 }}</small> <small class="text-muted">{{ candidate.resume.name|truncatechars:30 }}</small>
</div> </div> {% endcomment %}
<div class="d-flex flex-column gap-2"> <div class="d-flex flex-column gap-2">
<div class="d-flex gap-2"> <div class="d-flex gap-2">
<a href="{{ candidate.resume.url }}" target="_blank" class="btn btn-outline-primary"> <a href="{{ candidate.resume.url }}" target="_blank" class="btn btn-outline-primary">
<i class="fas fa-eye me-1"></i> <i class="fas fa-eye me-1"></i>
{% trans "View Resume" %} {% trans "View Actual Resume" %}
</a> </a>
<a href="{{ candidate.resume.url }}" download class="btn btn-main-action"> <a href="{{ candidate.resume.url }}" download class="btn btn-main-action">
<i class="fas fa-download me-1"></i> <i class="fas fa-download me-1"></i>
@ -311,7 +311,7 @@
</div> </div>
<a href="{% url 'candidate_resume_template' candidate.slug %}" class="btn btn-outline-info"> <a href="{% url 'candidate_resume_template' candidate.slug %}" class="btn btn-outline-info">
<i class="fas fa-file-alt me-1"></i> <i class="fas fa-file-alt me-1"></i>
{% trans "View Formatted Resume" %} {% trans "View Resume AI Overview" %}
</a> </a>
</div> </div>
</div> </div>

View File

@ -297,7 +297,7 @@ window.generateRandomKey = generateRandomKey;
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %} {% block customJS %}
<script> <script>
// Function to copy text to clipboard // Function to copy text to clipboard
function copyToClipboard(elementId) { function copyToClipboard(elementId) {