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 = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': 'norahuniversity',
|
||||
'USER': 'norahuniversity',
|
||||
'PASSWORD': 'norahuniversity',
|
||||
'NAME': 'haikal_db',
|
||||
'USER': 'faheed',
|
||||
'PASSWORD': 'Faheed@215',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '5432',
|
||||
}
|
||||
|
||||
@ -22,8 +22,8 @@ urlpatterns = [
|
||||
# path('', include('recruitment.urls')),
|
||||
path("ckeditor5/", include('django_ckeditor_5.urls')),
|
||||
|
||||
path('<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>/', views.form_wizard_view, name='form_wizard'),
|
||||
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/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):
|
||||
"""Form for creating and editing job postings"""
|
||||
|
||||
@ -298,12 +299,13 @@ class JobPostingForm(forms.ModelForm):
|
||||
fields = [
|
||||
'title', 'department', 'job_type', 'workplace_type',
|
||||
'location_city', 'location_state', 'location_country',
|
||||
'description', 'qualifications', 'salary_range', 'benefits','application_start_date'
|
||||
,'application_deadline', 'application_instructions',
|
||||
'position_number', 'reporting_to', 'joining_date',
|
||||
'created_by','open_positions','hash_tags','max_applications'
|
||||
'description', 'qualifications', 'salary_range', 'benefits',
|
||||
'application_deadline', 'application_instructions',
|
||||
'position_number', 'reporting_to',
|
||||
'open_positions', 'hash_tags', 'max_applications'
|
||||
]
|
||||
widgets = {
|
||||
# Basic Information
|
||||
'title': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Assistant Professor of Computer Science',
|
||||
@ -321,6 +323,8 @@ class JobPostingForm(forms.ModelForm):
|
||||
'class': 'form-select',
|
||||
'required': True
|
||||
}),
|
||||
|
||||
# Location
|
||||
'location_city': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Boston'
|
||||
@ -333,18 +337,25 @@ class JobPostingForm(forms.ModelForm):
|
||||
'class': 'form-control',
|
||||
'value': 'United States'
|
||||
}),
|
||||
|
||||
'salary_range': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '$60,000 - $80,000'
|
||||
}),
|
||||
'application_start_date': forms.DateInput(attrs={
|
||||
'class': 'form-control',
|
||||
'type': 'date'
|
||||
}),
|
||||
|
||||
# Application Information
|
||||
# 'application_url': forms.URLInput(attrs={
|
||||
# 'class': 'form-control',
|
||||
# 'placeholder': 'https://university.edu/careers/job123',
|
||||
# 'required': True
|
||||
# }),
|
||||
|
||||
'application_deadline': forms.DateInput(attrs={
|
||||
'class': 'form-control',
|
||||
'type': 'date'
|
||||
'type': 'date',
|
||||
'required': True
|
||||
}),
|
||||
|
||||
'open_positions': forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'min': 1,
|
||||
@ -353,7 +364,10 @@ class JobPostingForm(forms.ModelForm):
|
||||
'hash_tags': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': '#hiring,#jobopening',
|
||||
# 'validators':validate_hash_tags, # Assuming this is available
|
||||
}),
|
||||
|
||||
# Internal Information
|
||||
'position_number': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'UNIV-2025-001'
|
||||
@ -362,14 +376,7 @@ class JobPostingForm(forms.ModelForm):
|
||||
'class': 'form-control',
|
||||
'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={
|
||||
'class': 'form-control',
|
||||
'min': 1,
|
||||
@ -377,40 +384,51 @@ class JobPostingForm(forms.ModelForm):
|
||||
}),
|
||||
}
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
self.is_anonymous_user = kwargs.pop('is_anonymous_user', False)
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
# Now call the parent __init__ with remaining args
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if not self.instance.pk:
|
||||
if not self.is_anonymous_user:
|
||||
self.fields['created_by'].initial = 'University Administrator'
|
||||
self.fields['location_city'].initial='Riyadh'
|
||||
self.fields['location_state'].initial='Riyadh Province'
|
||||
self.fields['location_country'].initial='Saudi Arabia'
|
||||
if not self.instance.pk: # Creating new job posting
|
||||
# self.fields['status'].initial = 'Draft'
|
||||
self.fields['location_city'].initial = 'Riyadh'
|
||||
self.fields['location_state'].initial = 'Riyadh Province'
|
||||
self.fields['location_country'].initial = 'Saudi Arabia'
|
||||
|
||||
def clean_hash_tags(self):
|
||||
hash_tags=self.cleaned_data.get('hash_tags')
|
||||
hash_tags = self.cleaned_data.get('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:
|
||||
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 hash_tags
|
||||
return hash_tags # Allow blank
|
||||
|
||||
def clean_title(self):
|
||||
title=self.cleaned_data.get('title')
|
||||
if not title or len(title.strip())<3:
|
||||
title = self.cleaned_data.get('title')
|
||||
if not title or len(title.strip()) < 3:
|
||||
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.")
|
||||
return title.strip()
|
||||
|
||||
def clean_description(self):
|
||||
description=self.cleaned_data.get('description')
|
||||
if not description or len(description.strip())<20:
|
||||
description = self.cleaned_data.get('description')
|
||||
if not description or len(description.strip()) < 20:
|
||||
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 Meta:
|
||||
|
||||
@ -33,6 +33,7 @@ class Profile(models.Model):
|
||||
def __str__(self):
|
||||
return f"image for user {self.user}"
|
||||
|
||||
|
||||
class JobPosting(Base):
|
||||
# Basic Job Information
|
||||
JOB_TYPES = [
|
||||
@ -66,16 +67,16 @@ class JobPosting(Base):
|
||||
# Job Details
|
||||
description = CKEditor5Field(
|
||||
'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,
|
||||
config_name='extends'
|
||||
)
|
||||
qualifications = CKEditor5Field(blank=True, null=True,
|
||||
config_name='extends'
|
||||
)
|
||||
salary_range = models.CharField(
|
||||
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_url = models.URLField(
|
||||
@ -84,10 +85,10 @@ class JobPosting(Base):
|
||||
null=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_instructions =CKEditor5Field(
|
||||
blank=True, null=True,config_name='extends'
|
||||
|
||||
application_deadline = models.DateField(db_index=True) # Added index
|
||||
application_instructions = CKEditor5Field(
|
||||
blank=True, null=True, config_name='extends'
|
||||
)
|
||||
|
||||
# Internal Tracking
|
||||
@ -105,7 +106,7 @@ class JobPosting(Base):
|
||||
("ARCHIVED", "Archived"),
|
||||
]
|
||||
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
|
||||
@ -129,7 +130,7 @@ class JobPosting(Base):
|
||||
)
|
||||
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
|
||||
position_number = models.CharField(
|
||||
max_length=50, blank=True, help_text="University position number"
|
||||
@ -137,7 +138,7 @@ class JobPosting(Base):
|
||||
reporting_to = models.CharField(
|
||||
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(
|
||||
default=1, help_text="Number of open positions for this job"
|
||||
)
|
||||
@ -149,11 +150,11 @@ class JobPosting(Base):
|
||||
null=True,
|
||||
blank=True,
|
||||
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(
|
||||
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(
|
||||
"HiringAgency",
|
||||
blank=True,
|
||||
@ -181,7 +182,7 @@ class JobPosting(Base):
|
||||
verbose_name = "Job Posting"
|
||||
verbose_name_plural = "Job Postings"
|
||||
indexes = [
|
||||
models.Index(fields=['status', 'created_at','title']),
|
||||
models.Index(fields=['status', 'created_at', 'title']),
|
||||
models.Index(fields=['slug']),
|
||||
]
|
||||
|
||||
@ -258,7 +259,6 @@ class JobPosting(Base):
|
||||
# Return True if any content remains after stripping tags and spaces.
|
||||
return bool(final_content)
|
||||
|
||||
|
||||
@property
|
||||
def has_description_content(self):
|
||||
"""Returns True if the description field has meaningful content."""
|
||||
@ -277,6 +277,7 @@ class JobPosting(Base):
|
||||
@property
|
||||
def has_application_instructions_content(self):
|
||||
return self._check_content(self.application_instructions)
|
||||
|
||||
@property
|
||||
def current_applications_count(self):
|
||||
"""Returns the current number of candidates associated with this job."""
|
||||
@ -289,9 +290,13 @@ class JobPosting(Base):
|
||||
return True
|
||||
|
||||
return self.current_applications_count >= self.max_applications
|
||||
|
||||
@property
|
||||
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
|
||||
def screening_candidates(self):
|
||||
return self.all_candidates.filter(stage="Applied")
|
||||
@ -299,6 +304,7 @@ class JobPosting(Base):
|
||||
@property
|
||||
def exam_candidates(self):
|
||||
return self.all_candidates.filter(stage="Exam")
|
||||
|
||||
@property
|
||||
def interview_candidates(self):
|
||||
return self.all_candidates.filter(stage="Interview")
|
||||
@ -307,11 +313,13 @@ class JobPosting(Base):
|
||||
def offer_candidates(self):
|
||||
return self.all_candidates.filter(stage="Offer")
|
||||
|
||||
|
||||
#counts
|
||||
# counts
|
||||
@property
|
||||
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
|
||||
def screening_candidates_count(self):
|
||||
return self.all_candidates.filter(stage="Applied").count()
|
||||
@ -319,6 +327,7 @@ class JobPosting(Base):
|
||||
@property
|
||||
def exam_candidates_count(self):
|
||||
return self.all_candidates.filter(stage="Exam").count()
|
||||
|
||||
@property
|
||||
def interview_candidates_count(self):
|
||||
return self.all_candidates.filter(stage="Interview").count()
|
||||
@ -327,6 +336,7 @@ class JobPosting(Base):
|
||||
def offer_candidates_count(self):
|
||||
return self.all_candidates.filter(stage="Offer").count()
|
||||
|
||||
|
||||
class JobPostingImage(models.Model):
|
||||
job=models.OneToOneField('JobPosting',on_delete=models.CASCADE,related_name='post_images')
|
||||
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":
|
||||
form = JobPostingForm(
|
||||
request.POST, is_anonymous_user=not request.user.is_authenticated
|
||||
request.POST
|
||||
)
|
||||
# to check user is authenticated or not
|
||||
if form.is_valid():
|
||||
try:
|
||||
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_apply_url_relative=reverse('job_detail_candidate',kwargs={'slug':job.slug})
|
||||
job_apply_url_absolute=request.build_absolute_uri(job_apply_url_relative)
|
||||
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()
|
||||
messages.success(request, f'Job "{job.title}" created successfully!')
|
||||
return redirect("job_list")
|
||||
@ -302,7 +293,7 @@ def create_job(request):
|
||||
else:
|
||||
messages.error(request, f"Please correct the errors below.{form.errors}")
|
||||
else:
|
||||
form = JobPostingForm(is_anonymous_user=not request.user.is_authenticated)
|
||||
form = JobPostingForm()
|
||||
return render(request, "jobs/create_job.html", {"form": form})
|
||||
|
||||
|
||||
@ -313,21 +304,11 @@ def edit_job(request, slug):
|
||||
if request.method == "POST":
|
||||
form = JobPostingForm(
|
||||
request.POST,
|
||||
instance=job,
|
||||
is_anonymous_user=not request.user.is_authenticated,
|
||||
instance=job
|
||||
)
|
||||
if form.is_valid():
|
||||
try:
|
||||
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 = "University Administrator"
|
||||
job.save()
|
||||
form.save()
|
||||
messages.success(request, f'Job "{job.title}" updated successfully!')
|
||||
return redirect("job_list")
|
||||
except Exception as e:
|
||||
@ -338,7 +319,7 @@ def edit_job(request, slug):
|
||||
else:
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
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})
|
||||
|
||||
@ -2224,10 +2205,10 @@ def account_toggle_status(request,pk):
|
||||
messages.error(f'Please correct the error below')
|
||||
|
||||
|
||||
@login_required
|
||||
def user_detail(requests,pk):
|
||||
user=get_object_or_404(User,pk=pk)
|
||||
return render(requests,'user/profile.html')
|
||||
# @login_required
|
||||
# def user_detail(requests,pk):
|
||||
# user=get_object_or_404(User,pk=pk)
|
||||
# return render(requests,'user/profile.html')
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
<div class="en text-xs">King Abdullah bin Abdulaziz University Hospital</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>
|
||||
@ -55,11 +55,11 @@
|
||||
|
||||
{# --- 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">
|
||||
<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 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>
|
||||
|
||||
{# 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">
|
||||
<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">
|
||||
<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" />
|
||||
</svg>
|
||||
<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" />
|
||||
</svg>
|
||||
{% trans "Meetings" %}
|
||||
</span>
|
||||
</a>
|
||||
</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">
|
||||
<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">
|
||||
@ -364,4 +356,4 @@
|
||||
{% block customJS %}{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@ -71,7 +71,7 @@
|
||||
border: 1px solid #ced4da;
|
||||
width: 100%;
|
||||
padding: 0.375rem 0.75rem;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ================================================= */
|
||||
@ -100,7 +100,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="container-fluid py-4">
|
||||
<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 %}
|
||||
</h1>
|
||||
@ -150,8 +150,113 @@
|
||||
</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-header-themed">
|
||||
@ -179,21 +284,15 @@
|
||||
</div>
|
||||
|
||||
{# ================================================= #}
|
||||
{# SECTION 3: COMPENSATION AND APPLICATION #}
|
||||
{# SECTION 5: APPLICATION Instructions #}
|
||||
{# ================================================= #}
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<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 class="card-body">
|
||||
<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 %}
|
||||
|
||||
<div class="col-12">
|
||||
@ -215,117 +314,7 @@
|
||||
</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 #}
|
||||
|
||||
@ -71,7 +71,7 @@
|
||||
border: 1px solid #ced4da;
|
||||
width: 100%;
|
||||
padding: 0.375rem 0.75rem;
|
||||
box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ================================================= */
|
||||
@ -100,7 +100,7 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="container-fluid py-4">
|
||||
<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 %}
|
||||
</h1>
|
||||
@ -150,8 +150,113 @@
|
||||
</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-header-themed">
|
||||
@ -179,21 +284,15 @@
|
||||
</div>
|
||||
|
||||
{# ================================================= #}
|
||||
{# SECTION 3: COMPENSATION AND APPLICATION #}
|
||||
{# SECTION 5: APPLICATION Instructions #}
|
||||
{# ================================================= #}
|
||||
<div class="card mb-4 shadow-sm">
|
||||
<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 class="card-body">
|
||||
<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 %}
|
||||
|
||||
<div class="col-12">
|
||||
@ -215,117 +314,7 @@
|
||||
</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 #}
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
|
||||
{% extends "base.html" %}
|
||||
{% load i18n static %}
|
||||
|
||||
@ -217,8 +216,17 @@
|
||||
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>
|
||||
{% endblock %}
|
||||
@ -236,7 +244,7 @@
|
||||
<div class="row g-4">
|
||||
|
||||
{# LEFT COLUMN: JOB DETAILS WITH TABS #}
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-7">
|
||||
<div class="card shadow-sm no-hover">
|
||||
|
||||
{# HEADER SECTION #}
|
||||
@ -248,6 +256,7 @@
|
||||
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="badge status-badge">
|
||||
{# Corrected status badge logic to close the span correctly #}
|
||||
{% if job.status == "ACTIVE" %}
|
||||
<span class="badge bg-success status-badge">
|
||||
{% elif job.status == "DRAFT" %}
|
||||
@ -262,6 +271,7 @@
|
||||
<span class="badge bg-secondary status-badge">
|
||||
{% endif %}
|
||||
{{ 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">
|
||||
<i class="fas fa-edit text-primary"></i>
|
||||
</button>
|
||||
@ -281,13 +291,12 @@
|
||||
<i class="fas fa-file-alt me-1"></i> {% trans "Description & Requirements" %}
|
||||
</button>
|
||||
</li>
|
||||
{% if job.application_instructions %}
|
||||
<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">
|
||||
<i class="fas fa-paper-plane me-1"></i> {% trans "Application" %}
|
||||
<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-chart-line me-1"></i> {% trans "Application KPIs" %}
|
||||
</button>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
</ul>
|
||||
|
||||
<div class="card-body">
|
||||
@ -323,21 +332,20 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-main-action btn-sm"
|
||||
id="copyJobLinkButton"
|
||||
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">
|
||||
<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" />
|
||||
</svg>
|
||||
{% trans "Share Public Link" %}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-main-action btn-sm"
|
||||
id="copyJobLinkButton"
|
||||
data-url="{{ job.application_url }}">
|
||||
{# Replaced bulky SVG with simpler Font Awesome icon #}
|
||||
<i class="fas fa-link"></i>
|
||||
{% trans "Share Public Link" %}
|
||||
</button>
|
||||
|
||||
<span id="copyFeedback" class="text-success ms-2 small" style="display:none;">
|
||||
{% trans "Copied!" %}
|
||||
</span>
|
||||
</div>
|
||||
<span id="copyFeedback" class="text-success ms-2 small" style="display:none;">
|
||||
{% trans "Copied!" %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<h5 class="text-muted mb-3">{% trans "Financial & Timeline" %}</h5>
|
||||
@ -392,19 +400,74 @@
|
||||
<div class="text-secondary">{{ job.benefits|safe}}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# 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">
|
||||
{% if job.application_instructions %}
|
||||
<div class="mb-4">
|
||||
<h5>{% trans "Application Instructions" %}</h5>
|
||||
<div class="text-secondary">{{ job.application_instructions|safe }}</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>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{# FOOTER ACTIONS #}
|
||||
@ -423,7 +486,7 @@
|
||||
</div>
|
||||
|
||||
{# RIGHT COLUMN: TABBED CARDS #}
|
||||
<div class="col-lg-4 ">
|
||||
<div class="col-lg-5">
|
||||
|
||||
{# New Card for Candidate Category Chart #}
|
||||
<div class="card shadow-sm no-hover mb-4">
|
||||
@ -440,44 +503,9 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm no-hover mb-4">
|
||||
<div class="card-body p-4">
|
||||
<h6 class="text-muted mb-4">{% trans "Applicant Tracking" %}</h6>
|
||||
{% include 'jobs/partials/applicant_tracking.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{# REMOVED: Standalone Applicant Tracking Card (It is now in a tab) #}
|
||||
|
||||
<div class="stats">
|
||||
<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;">
|
||||
<div class="card shadow-sm no-hover">
|
||||
|
||||
{# RIGHT TABS NAVIGATION #}
|
||||
<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" %}
|
||||
</button>
|
||||
</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">
|
||||
<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" %}
|
||||
</button>
|
||||
</li>
|
||||
<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">
|
||||
<i class="fas fa-info me-1 text-muted"></i> {% trans "Linkedin" %}
|
||||
<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="fab fa-linkedin me-1 text-info"></i> {% trans "LinkedIn" %}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
@ -503,34 +536,6 @@
|
||||
{# TAB 1: APPLICANTS CONTENT #}
|
||||
<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>
|
||||
{% 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">
|
||||
<a href="{% url 'candidate_create_for_job' job.slug %}" class="btn btn-main-action">
|
||||
@ -542,13 +547,16 @@
|
||||
</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">
|
||||
|
||||
{# 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>
|
||||
<div class="d-grid gap-2">
|
||||
<p class="text-muted small mb-3">
|
||||
@ -569,8 +577,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# TAB 3: INTERNAL INFO CONTENT #}
|
||||
<div class="tab-pane fade" id="internal-pane" role="tabpanel" aria-labelledby="internal-tab">
|
||||
{# TAB 4: LINKEDIN INTEGRATION CONTENT #}
|
||||
<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>
|
||||
<div class="mb-4">
|
||||
{% if job.posted_to_linkedin %}
|
||||
@ -611,48 +619,6 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</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>
|
||||
@ -660,10 +626,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--image modal class-->
|
||||
{% 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-dialog">
|
||||
<div class="modal-content">
|
||||
@ -811,4 +775,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -278,7 +278,7 @@
|
||||
</tr>
|
||||
|
||||
<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 * 2);">{% trans "Exam" %}</th>
|
||||
<th style="width: calc(50% / 7 * 2);">{% trans "Interview" %}</th>
|
||||
@ -386,7 +386,7 @@
|
||||
{# --- END OF JOB LIST CONTAINER --- #}
|
||||
{% include "includes/paginator.html" %}
|
||||
{% 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">
|
||||
<i class="fas fa-briefcase fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
|
||||
<h3>{% trans "No job postings found" %}</h3>
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
|
||||
{% extends 'base.html' %}
|
||||
{% load static i18n %}
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* KAAT-S Redesign CSS */
|
||||
/* KAAT-S Redesign CSS - Optimized Compact Detail View (Settings Removed) */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
:root {
|
||||
@ -20,93 +19,56 @@
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f0f2f5; /* Off-white page background */
|
||||
font-family: 'Inter', sans-serif; /* Use a modern font stack */
|
||||
background-color: #f0f2f5;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
/* ------------------ General Layout & Card Styles ------------------ */
|
||||
|
||||
.container {
|
||||
width:auto;
|
||||
padding: 3rem 1.5rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
border: none; /* Remove default border */
|
||||
border: none;
|
||||
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 */
|
||||
background-color: white;
|
||||
margin-bottom: 2.5rem;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 5px 15px rgba(0,0,0,0.05), 0 2px 5px rgba(0,0,0,0.03);
|
||||
margin-bottom: 1.5rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.card:not(.no-hover):hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 15px 40px rgba(0,0,0,0.1), 0 6px 15px 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);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(0,0,0,0.08);
|
||||
}
|
||||
.card-body {
|
||||
padding: 2rem;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
/* ------------------ Header & Title Styles ------------------ */
|
||||
/* ------------------ Main Header & Title Styles ------------------ */
|
||||
|
||||
.card-header {
|
||||
background-color: var(--kaauh-gray-light);
|
||||
border-bottom: 1px solid var(--kaauh-border);
|
||||
padding: 2rem;
|
||||
.main-title-card {
|
||||
padding: 1.5rem 2rem;
|
||||
background-color: white;
|
||||
border-bottom: 3px solid var(--kaauh-teal);
|
||||
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 {
|
||||
font-size: 0.85rem;
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 20px; /* Pill shape */
|
||||
font-size: 0.75rem;
|
||||
padding: 0.35em 0.8em;
|
||||
border-radius: 15px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
.bg-waiting { background-color: #ffc107 !important; color: var(--kaauh-primary-text) !important;}
|
||||
.bg-started { background-color: var(--kaauh-teal) !important; color: white !important;}
|
||||
@ -114,13 +76,13 @@ body {
|
||||
|
||||
/* ------------------ Detail Row & Content Styles ------------------ */
|
||||
|
||||
.card h2 {
|
||||
.detail-section h2 {
|
||||
color: var(--kaauh-teal-dark);
|
||||
font-weight: 700;
|
||||
padding: 1.5rem 2rem 1rem;
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
border-bottom: 1px solid var(--kaauh-border);
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 2px solid var(--kaauh-teal-light);
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-row-group {
|
||||
@ -128,10 +90,10 @@ body {
|
||||
}
|
||||
|
||||
.detail-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(150px, 40%) 1fr;
|
||||
padding: 1rem 2rem;
|
||||
border-bottom: 1px solid var(--kaauh-border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px dashed var(--kaauh-border);
|
||||
align-items: center;
|
||||
}
|
||||
.detail-row:last-child {
|
||||
@ -139,273 +101,194 @@ body {
|
||||
}
|
||||
.detail-label {
|
||||
font-weight: 600;
|
||||
color: var(--kaauh-teal-dark);
|
||||
text-align: left;
|
||||
font-size: 0.95rem;
|
||||
color: var(--kaauh-teal);
|
||||
font-size: 0.9rem;
|
||||
flex-basis: 45%;
|
||||
}
|
||||
.detail-value {
|
||||
text-align: right;
|
||||
color: var(--kaauh-primary-text);
|
||||
word-wrap: break-word;
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
font-size: 0.9rem;
|
||||
flex-basis: 55%;
|
||||
}
|
||||
|
||||
/* ------------------ Join Info & Copy Button ------------------ */
|
||||
|
||||
.join-info-card .card-body {
|
||||
padding-top: 2rem;
|
||||
.join-info-card {
|
||||
border-left: 5px solid var(--kaauh-teal); /* Highlight join info */
|
||||
}
|
||||
.btn-primary {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0.6rem 1.25rem;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.btn-primary:hover {
|
||||
background-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 {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
margin-top: 1.5rem;
|
||||
position: relative;
|
||||
padding: 1rem 0; /* Add padding for clear space around the copy area */
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.join-url-display {
|
||||
flex-grow: 1;
|
||||
background-color: var(--kaauh-gray-light);
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 8px;
|
||||
padding: 0.75rem 1rem;
|
||||
word-break: break-all;
|
||||
font-size: 0.9rem;
|
||||
color: var(--kaauh-secondary-text);
|
||||
font-family: monospace; /* Monospace for links/code */
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.join-url-display strong {
|
||||
color: var(--kaauh-teal-dark);
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
|
||||
.btn-copy {
|
||||
flex-shrink: 0;
|
||||
background-color: var(--kaauh-teal-dark); /* Darker teal for a clean utility look */
|
||||
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);
|
||||
padding: 0.5rem 0.75rem;
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
}
|
||||
|
||||
/* ------------------ Footer & Actions ------------------ */
|
||||
|
||||
.card-footer {
|
||||
.action-bar-footer {
|
||||
border-top: 1px solid var(--kaauh-border);
|
||||
padding: 1.5rem 2rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: flex-start;
|
||||
padding: 1rem 1.5rem;
|
||||
gap: 0.75rem;
|
||||
background-color: var(--kaauh-gray-light);
|
||||
border-radius: 0 0 12px 12px;
|
||||
}
|
||||
.btn-danger {
|
||||
background-color: var(--kaauh-danger);
|
||||
border-color: var(--kaauh-danger);
|
||||
color: white;
|
||||
.btn-footer-action {
|
||||
font-weight: 600;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
border-color: #bd2130;
|
||||
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
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 {
|
||||
/* ------------------ Comments Section ------------------ */
|
||||
#comments-card .card-header {
|
||||
background-color: var(--kaauh-teal-light);
|
||||
color: var(--kaauh-teal-dark);
|
||||
font-weight: 700;
|
||||
font-size: 1.35rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
#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;
|
||||
padding: 1rem 1.5rem;
|
||||
font-weight: 600;
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
/* Comment card body/item styling is kept compact */
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="card no-hover">
|
||||
<div class="card-header">
|
||||
<div class="card-header-title-group">
|
||||
<h1>
|
||||
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="[http://www.w3.org/2000/svg](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="col-auto">
|
||||
<span class="status-badge bg-{{ meeting.status }}">
|
||||
{{ meeting.status|title }}
|
||||
</span>
|
||||
{# --- TOP BAR / BACK BUTTON --- #}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<a href="{% url 'list_meetings' %}" class="btn btn-secondary-back">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Meetings" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
{# --- LEFT COLUMN (MAIN DETAILS) --- #}
|
||||
<div class="col-lg-6">
|
||||
<div class="card no-hover h-100">
|
||||
{# --- CONSOLIDATED HEADER --- #}
|
||||
<div class="main-title-card">
|
||||
<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>
|
||||
{% if meeting.interview %}
|
||||
<div class="col-auto">
|
||||
<span class="status-badge">
|
||||
Candidate Name : <a class="text-primary-theme" href="{% url 'candidate_detail' meeting.interview.candidate.slug %}">{{ meeting.interview.candidate.name }} </a>
|
||||
</span>
|
||||
|
||||
{# --- MAIN DETAIL BODY --- #}
|
||||
<div class="card-body detail-section">
|
||||
<h2>{% trans "Core Details" %}</h2>
|
||||
<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>
|
||||
{% endif %}
|
||||
</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>
|
||||
|
||||
<div class="card no-hover">
|
||||
<h2>{% trans "Meeting Information" %}</h2>
|
||||
<div class="card-body 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 "Topic" %}:</div><div class="detail-value">{{ meeting.topic }}</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>
|
||||
{% if meeting.join_url %}
|
||||
<div class="card no-hover join-info-card">
|
||||
<h2>{% trans "Join Information" %}</h2>
|
||||
<div class="card-body">
|
||||
<a href="{{ meeting.join_url }}" class="btn btn-primary" target="_blank">
|
||||
<i class="fas fa-video"></i> {% trans "Join Meeting Now" %}
|
||||
</a>
|
||||
|
||||
<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>
|
||||
{# --- ACTION BAR AT THE BOTTOM OF THE MAIN CARD --- #}
|
||||
<div class="card-footer action-bar-footer d-flex justify-content-end">
|
||||
<a href="{% url 'update_meeting' meeting.slug %}" class="btn btn-primary btn-footer-action">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Update" %}
|
||||
</a>
|
||||
{% if meeting.zoom_gateway_response %}
|
||||
<button type="button" class="btn btn-secondary btn-footer-action" onclick="toggleGateway()">
|
||||
<i class="fas fa-code me-1"></i> {% trans "API Response" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
<button type="button" class="btn btn-danger btn-footer-action" 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 me-1"></i>
|
||||
Delete
|
||||
</button>
|
||||
</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>
|
||||
{% endif %}
|
||||
|
||||
<div class="card no-hover">
|
||||
<h2>{% trans "Settings" %}</h2>
|
||||
<div class="card-body detail-row-group">
|
||||
<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="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="detail-row"><div class="detail-label">{% trans "Mute Upon Entry" %}:</div><div class="detail-value">{{ meeting.mute_upon_entry|yesno:"Yes,No" }}</div></div>
|
||||
<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>
|
||||
{# --- RIGHT COLUMN (JOIN INFO) --- #}
|
||||
<div class="col-lg-6">
|
||||
{% if meeting.join_url %}
|
||||
<div class="card no-hover join-info-card detail-section h-100">
|
||||
<div class="card-body">
|
||||
<h2>{% trans "Join Information" %}</h2>
|
||||
|
||||
<div class="card no-hover">
|
||||
<div class="card-footer">
|
||||
<a href="{% url 'update_meeting' meeting.slug %}" class="btn btn-primary">
|
||||
<i class="fas fa-edit"></i> {% trans "Update Meeting" %}
|
||||
</a>
|
||||
<a href="{{ meeting.join_url }}" class="btn btn-primary w-100 mb-4" target="_blank">
|
||||
<i class="fas fa-video me-1"></i> {% trans "Join Meeting Now" %}
|
||||
</a>
|
||||
|
||||
{% if meeting.zoom_gateway_response %}
|
||||
<button type="button" class="btn btn-secondary" onclick="toggleGateway()">
|
||||
<i class="fas fa-code"></i> {% trans "View API Response" %}
|
||||
</button>
|
||||
<div class="join-url-container">
|
||||
<div id="copy-message" style="opacity: 0;">{% trans "Copied!" %}</div>
|
||||
|
||||
<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 %}
|
||||
<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>
|
||||
|
||||
{# --- API RESPONSE CARD (Full width, hidden by default) --- #}
|
||||
{% 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">
|
||||
<h3>{% trans "API Gateway Response" %}</h3>
|
||||
<pre>{{ meeting.zoom_gateway_response|safe }}</pre>
|
||||
@ -413,72 +296,68 @@ body {
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Comments Section -->
|
||||
<div class="card no-hover" id="comments-card">
|
||||
<div class="card-header text-primary-theme d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">
|
||||
|
||||
{# --- Comments Section (Full Width, below main content) --- #}
|
||||
<div class="card no-hover mt-4" id="comments-card">
|
||||
<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>
|
||||
Comments ({{ meeting.comments.count }})
|
||||
{% trans "Comments" %} ({{ meeting.comments.count }})
|
||||
</h5>
|
||||
{% if user.is_authenticated %}
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
hx-get="{% url 'add_meeting_comment' meeting.slug %}"
|
||||
hx-target="#comment-section"
|
||||
>
|
||||
<i class="fas fa-plus"></i> Add Comment
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add Comment" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="comment-section">
|
||||
{% if meeting.comments.all %}
|
||||
<div class="row">
|
||||
{% for comment in meeting.comments.all|dictsortreversed:"created_at" %}
|
||||
<div class="col-12 mb-3">
|
||||
<div class="card ">
|
||||
<div class="card-header d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<strong>{{ comment.author.get_full_name|default:comment.author.username }}</strong>
|
||||
{% if comment.author != user %}
|
||||
<span class="badge bg-secondary ms-2">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>
|
||||
{% for comment in meeting.comments.all|dictsortreversed:"created_at" %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong class="me-2">{{ comment.author.get_full_name|default:comment.author.username }}</strong>
|
||||
{% if comment.author != user %}
|
||||
<span class="badge bg-secondary ms-1">{% trans "Comment" %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<small class="text-muted">{{ comment.created_at|date:"M d, Y P" }}</small>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</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="{% 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 %}
|
||||
<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 %}
|
||||
</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-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
@ -487,8 +366,7 @@ body {
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body" id="commentModalBody">
|
||||
<!-- HTMX will load the form here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -505,41 +383,36 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
// CopyLink function remains the same (as provided in the original code)
|
||||
function copyLink() {
|
||||
const urlElement = document.getElementById('meeting-join-url');
|
||||
const messageElement = document.getElementById('copy-message');
|
||||
const textToCopy = urlElement.textContent || urlElement.innerText;
|
||||
|
||||
// Clear any existing message
|
||||
clearTimeout(window.copyMessageTimeout);
|
||||
|
||||
// Function to show the message
|
||||
function showMessage(success) {
|
||||
messageElement.textContent = success ? '{% trans "Copied!" %}' : '{% trans "Copy Failed." %}';
|
||||
messageElement.style.backgroundColor = success ? 'var(--kaauh-success)' : 'var(--kaauh-danger)';
|
||||
messageElement.style.opacity = '1';
|
||||
|
||||
// Hide the message after 2 seconds
|
||||
window.copyMessageTimeout = setTimeout(() => {
|
||||
messageElement.style.opacity = '0';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Use the modern clipboard API
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
navigator.clipboard.writeText(textToCopy).then(() => {
|
||||
showMessage(true); // Show success message
|
||||
showMessage(true);
|
||||
}).catch(err => {
|
||||
console.error('Could not copy text: ', err);
|
||||
fallbackCopyTextToClipboard(textToCopy, showMessage); // Try fallback on failure
|
||||
fallbackCopyTextToClipboard(textToCopy, showMessage);
|
||||
});
|
||||
} else {
|
||||
// Fallback for older browsers
|
||||
fallbackCopyTextToClipboard(textToCopy, showMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback function for older browsers
|
||||
function fallbackCopyTextToClipboard(text, callback) {
|
||||
const textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
@ -560,7 +433,7 @@ body {
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
callback(success); // Call the message function with the result
|
||||
callback(success);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -294,15 +294,15 @@
|
||||
<div class="tab-pane fade" id="resume-pane" role="tabpanel" aria-labelledby="resume-tab">
|
||||
<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>
|
||||
{% comment %} <div>
|
||||
<p class="mb-1"><strong>{{ candidate.resume.name }}</strong></p>
|
||||
<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 gap-2">
|
||||
<a href="{{ candidate.resume.url }}" target="_blank" class="btn btn-outline-primary">
|
||||
<i class="fas fa-eye me-1"></i>
|
||||
{% trans "View Resume" %}
|
||||
{% trans "View Actual Resume" %}
|
||||
</a>
|
||||
<a href="{{ candidate.resume.url }}" download class="btn btn-main-action">
|
||||
<i class="fas fa-download me-1"></i>
|
||||
@ -311,7 +311,7 @@
|
||||
</div>
|
||||
<a href="{% url 'candidate_resume_template' candidate.slug %}" class="btn btn-outline-info">
|
||||
<i class="fas fa-file-alt me-1"></i>
|
||||
{% trans "View Formatted Resume" %}
|
||||
{% trans "View Resume AI Overview" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user