new chnage

This commit is contained in:
Faheed 2025-10-22 14:38:42 +03:00
parent ee78018a5a
commit 05e3271152
10 changed files with 645 additions and 821 deletions

View File

@ -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',
}

View File

@ -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:

View File

@ -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])

View File

@ -273,21 +273,12 @@ 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)
@ -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})

View File

@ -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 #}

View File

@ -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 #}

View File

@ -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 %}

View File

@ -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>

View File

@ -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 %}

View File

@ -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>