new chnage
This commit is contained in:
parent
ee78018a5a
commit
05e3271152
@ -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',
|
||||
}
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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})
|
||||
|
||||
|
||||
@ -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