Compare commits
2 Commits
1f3c38d687
...
f0d3218caa
| Author | SHA1 | Date | |
|---|---|---|---|
| f0d3218caa | |||
| 05e3271152 |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -135,9 +135,9 @@ WSGI_APPLICATION = 'NorahUniversity.wsgi.application'
|
|||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
'default': {
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||||
'NAME': 'norahuniversity',
|
'NAME': 'haikal_db',
|
||||||
'USER': 'norahuniversity',
|
'USER': 'faheed',
|
||||||
'PASSWORD': 'norahuniversity',
|
'PASSWORD': 'Faheed@215',
|
||||||
'HOST': '127.0.0.1',
|
'HOST': '127.0.0.1',
|
||||||
'PORT': '5432',
|
'PORT': '5432',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,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'),
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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:
|
||||||
|
|||||||
@ -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])
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -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
|
||||||
|
|||||||
@ -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">
|
||||||
@ -364,4 +356,4 @@
|
|||||||
{% block customJS %}{% endblock %}
|
{% block customJS %}{% endblock %}
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@ -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 #}
|
||||||
|
|||||||
@ -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 #}
|
||||||
|
|||||||
@ -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 %}
|
||||||
@ -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>
|
||||||
|
|||||||
@ -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 %}
|
||||||
@ -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>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user