....
This commit is contained in:
parent
fec156fab2
commit
f33cf97975
Binary file not shown.
Binary file not shown.
@ -135,9 +135,9 @@ WSGI_APPLICATION = 'NorahUniversity.wsgi.application'
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': 'norahuniversity',
|
||||
'USER': 'norahuniversity',
|
||||
'PASSWORD': 'norahuniversity',
|
||||
'NAME': 'haikal_db',
|
||||
'USER': 'faheed',
|
||||
'PASSWORD': 'Faheed@215',
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': '5432',
|
||||
}
|
||||
|
||||
@ -22,8 +22,8 @@ urlpatterns = [
|
||||
# path('', include('recruitment.urls')),
|
||||
path("ckeditor5/", include('django_ckeditor_5.urls')),
|
||||
|
||||
path('<slug:template_slug>/', views.form_wizard_view, name='form_wizard'),
|
||||
path('<slug:template_slug>/submit/', views.submit_form, name='submit_form'),
|
||||
path('form_wizard/<slug:template_slug>/', views.form_wizard_view, name='form_wizard'),
|
||||
path('form/<slug:template_slug>/submit/', views.submit_form, name='submit_form'),
|
||||
|
||||
path('api/templates/', views.list_form_templates, name='list_form_templates'),
|
||||
path('api/templates/save/', views.save_form_template, name='save_form_template'),
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -197,10 +197,10 @@ 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
|
||||
@ -249,13 +249,11 @@ class JobPostingForm(forms.ModelForm):
|
||||
# 'placeholder': 'https://university.edu/careers/job123',
|
||||
# 'required': True
|
||||
# }),
|
||||
'application_start_date': forms.DateInput(attrs={
|
||||
'class': 'form-control',
|
||||
'type': 'date'
|
||||
}),
|
||||
|
||||
'application_deadline': forms.DateInput(attrs={
|
||||
'class': 'form-control',
|
||||
'type': 'date'
|
||||
'type': 'date',
|
||||
'required':True
|
||||
}),
|
||||
|
||||
'open_positions': forms.NumberInput(attrs={
|
||||
@ -278,15 +276,8 @@ 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,
|
||||
@ -296,19 +287,15 @@ class JobPostingForm(forms.ModelForm):
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
|
||||
# Extract your custom argument BEFORE calling super()
|
||||
self.is_anonymous_user = kwargs.pop('is_anonymous_user', False)
|
||||
# Now call the parent __init__ with remaining args
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if not self.instance.pk:# Creating new job posting
|
||||
if not self.is_anonymous_user:
|
||||
self.fields['created_by'].initial = 'University Administrator'
|
||||
# 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')
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-21 11:27
|
||||
# Generated by Django 5.2.7 on 2025-10-21 22:26
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
@ -236,8 +236,8 @@ class Migration(migrations.Migration):
|
||||
('salary_range', models.CharField(blank=True, help_text='e.g., $60,000 - $80,000', max_length=200)),
|
||||
('benefits', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)),
|
||||
('application_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])),
|
||||
('application_start_date', models.DateField(blank=True, null=True)),
|
||||
('application_deadline', models.DateField(blank=True, db_index=True, null=True)),
|
||||
('application_start_date', models.DateField()),
|
||||
('application_deadline', models.DateField(db_index=True)),
|
||||
('application_instructions', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=True)),
|
||||
('internal_job_id', models.CharField(editable=False, max_length=50, primary_key=True, serialize=False)),
|
||||
('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)),
|
||||
@ -251,7 +251,6 @@ class Migration(migrations.Migration):
|
||||
('published_at', models.DateTimeField(blank=True, db_index=True, null=True)),
|
||||
('position_number', models.CharField(blank=True, help_text='University position number', max_length=50)),
|
||||
('reporting_to', models.CharField(blank=True, help_text='Who this position reports to', max_length=100)),
|
||||
('joining_date', models.DateField(blank=True, help_text='Desired start date', null=True)),
|
||||
('open_positions', models.PositiveIntegerField(default=1, help_text='Number of open positions for this job')),
|
||||
('max_applications', models.PositiveIntegerField(blank=True, default=1000, help_text='Maximum number of applications allowed', null=True)),
|
||||
('cancel_reason', models.TextField(blank=True, help_text='Reason for canceling the job posting', verbose_name='Cancel Reason')),
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-21 23:25
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('recruitment', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='jobposting',
|
||||
name='application_start_date',
|
||||
),
|
||||
]
|
||||
@ -84,8 +84,8 @@ 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_deadline = models.DateField(db_index=True) # Added index
|
||||
application_instructions =CKEditor5Field(
|
||||
blank=True, null=True,config_name='extends'
|
||||
)
|
||||
@ -137,7 +137,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"
|
||||
)
|
||||
@ -732,8 +732,10 @@ class FormTemplate(Base):
|
||||
blank=True, help_text="Description of the form template"
|
||||
)
|
||||
created_by = models.ForeignKey(
|
||||
User, on_delete=models.CASCADE, related_name="form_templates",null=True,blank=True, db_index=True
|
||||
)
|
||||
User, on_delete=models.CASCADE, related_name="form_templates",null=True,blank=True, db_index=True
|
||||
)
|
||||
# FIXME: on Delete model SETNULl
|
||||
|
||||
is_active = models.BooleanField(
|
||||
default=False, help_text="Whether this template is active"
|
||||
)
|
||||
|
||||
@ -10,7 +10,7 @@ logger = logging.getLogger(__name__)
|
||||
@receiver(post_save, sender=JobPosting)
|
||||
def format_job(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
FormTemplate.objects.create(job=instance, is_active=True, name=instance.title)
|
||||
# FormTemplate.objects.create(job=instance, is_active=True, name=instance.title)
|
||||
async_task(
|
||||
'recruitment.tasks.format_job_description',
|
||||
instance.pk,
|
||||
|
||||
@ -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})
|
||||
|
||||
@ -347,6 +328,7 @@ def edit_job(request, slug):
|
||||
def job_detail(request, slug):
|
||||
"""View details of a specific job"""
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
|
||||
# Get all candidates for this job, ordered by most recent
|
||||
applicants = job.candidates.all().order_by("-created_at")
|
||||
|
||||
@ -632,15 +614,15 @@ def application_success(request,slug):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@login_required
|
||||
def form_builder(request, template_slug=None):
|
||||
def form_builder(request, template_id=None):
|
||||
"""Render the form builder interface"""
|
||||
context = {}
|
||||
if template_slug:
|
||||
if template_id:
|
||||
template = get_object_or_404(
|
||||
FormTemplate, slug=template_slug
|
||||
FormTemplate, id=template_id, created_by=request.user
|
||||
)
|
||||
context['template']=template
|
||||
context["template_slug"] = template.slug
|
||||
context["template_id"] = template.id
|
||||
context["template_name"] = template.name
|
||||
return render(request, "forms/form_builder.html", context)
|
||||
|
||||
@ -653,12 +635,12 @@ def save_form_template(request):
|
||||
data = json.loads(request.body)
|
||||
template_name = data.get("name", "Untitled Form")
|
||||
stages_data = data.get("stages", [])
|
||||
template_slug = data.get("template_slug")
|
||||
template_id = data.get("template_id")
|
||||
|
||||
if template_slug:
|
||||
if template_id:
|
||||
# Update existing template
|
||||
template = get_object_or_404(
|
||||
FormTemplate, slug=template_slug
|
||||
FormTemplate, id=template_id, created_by=request.user
|
||||
)
|
||||
template.name = template_name
|
||||
template.save()
|
||||
@ -667,7 +649,7 @@ def save_form_template(request):
|
||||
else:
|
||||
# Create new template
|
||||
template = FormTemplate.objects.create(
|
||||
name=template_name
|
||||
name=template_name, created_by=request.user
|
||||
)
|
||||
|
||||
# Create stages and fields
|
||||
@ -703,7 +685,7 @@ def save_form_template(request):
|
||||
return JsonResponse(
|
||||
{
|
||||
"success": True,
|
||||
"template_slug": template.slug,
|
||||
"template_id": template.id,
|
||||
"message": "Form template saved successfully!",
|
||||
}
|
||||
)
|
||||
@ -712,9 +694,9 @@ def save_form_template(request):
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def load_form_template(request, template_slug):
|
||||
def load_form_template(request, template_id):
|
||||
"""Load an existing form template"""
|
||||
template = get_object_or_404(FormTemplate, slug=template_slug)
|
||||
template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user)
|
||||
|
||||
stages = []
|
||||
for stage in template.stages.all():
|
||||
@ -747,7 +729,6 @@ def load_form_template(request, template_slug):
|
||||
"success": True,
|
||||
"template": {
|
||||
"id": template.id,
|
||||
"template_slug": template.slug,
|
||||
"name": template.name,
|
||||
"description": template.description,
|
||||
"is_active": template.is_active,
|
||||
@ -762,7 +743,7 @@ def load_form_template(request, template_slug):
|
||||
def form_templates_list(request):
|
||||
"""List all form templates for the current user"""
|
||||
query = request.GET.get("q", "")
|
||||
templates = FormTemplate.objects.filter()
|
||||
templates = FormTemplate.objects.filter(created_by=request.user)
|
||||
|
||||
if query:
|
||||
templates = templates.filter(
|
||||
@ -802,7 +783,7 @@ def create_form_template(request):
|
||||
@require_http_methods(["GET"])
|
||||
def list_form_templates(request):
|
||||
"""List all form templates for the current user"""
|
||||
templates = FormTemplate.objects.filter().values(
|
||||
templates = FormTemplate.objects.filter(created_by=request.user).values(
|
||||
"id", "name", "description", "created_at", "updated_at"
|
||||
)
|
||||
return JsonResponse({"success": True, "templates": list(templates)})
|
||||
@ -812,25 +793,26 @@ def list_form_templates(request):
|
||||
@require_http_methods(["DELETE"])
|
||||
def delete_form_template(request, template_id):
|
||||
"""Delete a form template"""
|
||||
template = get_object_or_404(FormTemplate, id=template_id)
|
||||
template = get_object_or_404(FormTemplate, id=template_id, created_by=request.user)
|
||||
template.delete()
|
||||
return JsonResponse(
|
||||
{"success": True, "message": "Form template deleted successfully!"}
|
||||
)
|
||||
|
||||
|
||||
def form_wizard_view(request, template_slug):
|
||||
def form_wizard_view(request, template_id):
|
||||
"""Display the form as a step-by-step wizard"""
|
||||
template = get_object_or_404(FormTemplate, slug=template_slug, is_active=True)
|
||||
template = get_object_or_404(FormTemplate, pk=template_id, is_active=True)
|
||||
job_id = template.job.internal_job_id
|
||||
job=template.job
|
||||
is_limit_exceeded = job.is_application_limit_reached
|
||||
is_limit_exceeded=job.is_application_limit_reached
|
||||
if is_limit_exceeded:
|
||||
messages.error(
|
||||
request,
|
||||
'Application limit reached: This job is no longer accepting new applications. Please explore other available positions.'
|
||||
)
|
||||
return redirect('job_detail_candidate',slug=job.slug)
|
||||
|
||||
if job.is_expired:
|
||||
messages.error(
|
||||
request,
|
||||
@ -841,16 +823,14 @@ def form_wizard_view(request, template_slug):
|
||||
return render(
|
||||
request,
|
||||
"forms/form_wizard.html",
|
||||
{"template_slug": template_slug, "job_id": job_id},
|
||||
{"template_id": template_id, "job_id": job_id},
|
||||
)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
def submit_form(request, template_slug):
|
||||
def submit_form(request, template_id):
|
||||
"""Handle form submission"""
|
||||
template = get_object_or_404(FormTemplate, slug=template_slug)
|
||||
job = template.job
|
||||
template = get_object_or_404(FormTemplate, id=template_id)
|
||||
if request.method == "POST":
|
||||
try:
|
||||
with transaction.atomic():
|
||||
@ -864,7 +844,6 @@ def submit_form(request, template_slug):
|
||||
{"success": False, "message": "Application limit reached for this job."}
|
||||
)
|
||||
submission = FormSubmission.objects.create(template=template)
|
||||
|
||||
# Process field responses
|
||||
for field_id, value in request.POST.items():
|
||||
if field_id.startswith("field_"):
|
||||
@ -909,7 +888,7 @@ def submit_form(request, template_slug):
|
||||
)
|
||||
submission.applicant_email = email.display_value
|
||||
submission.save()
|
||||
# time=timezone.now()
|
||||
time=timezone.now()
|
||||
Candidate.objects.create(
|
||||
first_name=first_name.display_value,
|
||||
last_name=last_name.display_value,
|
||||
@ -917,16 +896,10 @@ def submit_form(request, template_slug):
|
||||
phone=phone.display_value,
|
||||
address=address.display_value,
|
||||
resume=resume.get_file if resume.is_file else None,
|
||||
job=job
|
||||
job=submission.template.job,
|
||||
|
||||
)
|
||||
return JsonResponse(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Form submitted successfully!",
|
||||
"redirect_url": reverse('application_success',kwargs={'slug':job.slug}),
|
||||
}
|
||||
)
|
||||
# return redirect('application_success',slug=job.slug)
|
||||
return redirect('application_success',slug=job.slug)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Candidate creation failed,{e}")
|
||||
@ -2071,6 +2044,7 @@ def user_detail(request, pk):
|
||||
user = get_object_or_404(User, pk=pk)
|
||||
|
||||
try:
|
||||
|
||||
profile_instance = user.profile
|
||||
profile_form = ProfileImageUploadForm(instance=profile_instance)
|
||||
except:
|
||||
@ -2224,10 +2198,6 @@ def account_toggle_status(request,pk):
|
||||
messages.error(f'Please correct the error below')
|
||||
|
||||
|
||||
@login_required
|
||||
def user_detail(requests,pk):
|
||||
user=get_object_or_404(User,pk=pk)
|
||||
return render(requests,'user/profile.html')
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
|
||||
@ -394,6 +394,27 @@ def dashboard_view(request):
|
||||
).count()
|
||||
high_potential_ratio = round((high_potential_count / total_candidates) * 100, 1) if total_candidates > 0 else 0
|
||||
|
||||
#donut chart data
|
||||
jobs=models.JobPosting.objects.all()
|
||||
selected_job_id=request.GET.get('selected_job_id')
|
||||
print(jobs)
|
||||
print(selected_job_id)
|
||||
apply_counts,exam_counts,interview_counts,offer_counts=[0]*4
|
||||
if selected_job_id:
|
||||
job=jobs.filter(internal_job_id=selected_job_id)
|
||||
apply_counts=job.screening_candidates_count or 0
|
||||
exam_counts=job.exam_candidates_count or 0
|
||||
interview_counts=job.interview_candidates_count or 0
|
||||
offer_counts=job.offer_candidates_count or 0
|
||||
|
||||
|
||||
|
||||
|
||||
applicant_stages=['APPLIED','EXAM','INTERVIEW','OFFER']
|
||||
stage_counts=[apply_counts,exam_counts,interview_counts,offer_counts]
|
||||
|
||||
|
||||
|
||||
|
||||
context = {
|
||||
'total_jobs': total_jobs,
|
||||
@ -409,8 +430,13 @@ def dashboard_view(request):
|
||||
'high_potential_count': high_potential_count,
|
||||
'high_potential_ratio': high_potential_ratio,
|
||||
'scored_ratio': scored_ratio,
|
||||
'applicant_stages':json.dumps(applicant_stages),
|
||||
'stage_counts':json.dumps(stage_counts),
|
||||
'jobs':'jobs',
|
||||
'selected_job_id':selected_job_id
|
||||
}
|
||||
return render(request, 'recruitment/dashboard.html', context)
|
||||
|
||||
@login_required
|
||||
def candidate_offer_view(request, slug):
|
||||
"""View for candidates in the Offer stage"""
|
||||
|
||||
@ -16,9 +16,9 @@
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
|
||||
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
||||
|
||||
|
||||
@ -120,7 +120,7 @@
|
||||
{% endif %}
|
||||
</button>
|
||||
<ul
|
||||
|
||||
|
||||
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
|
||||
style="min-width: 240px;"
|
||||
>
|
||||
@ -324,34 +324,6 @@
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function form_loader(){
|
||||
const forms = document.querySelectorAll('form');
|
||||
forms.forEach(form => {
|
||||
form.addEventListener('submit', function(e) {
|
||||
const submitButton = form.querySelector('button[type="submit"], input[type="submit"]');
|
||||
|
||||
if (submitButton) {
|
||||
submitButton.disabled = true;
|
||||
submitButton.classList.add('loading');
|
||||
window.addEventListener('unload', function() {
|
||||
submitButton.disabled = false;
|
||||
submitButton.classList.remove('loading');
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
form_loader();
|
||||
|
||||
try{
|
||||
document.addEventListener('htmx:afterSwap', form_loader);
|
||||
}catch(e){
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
</script>
|
||||
{% block customJS %}{% endblock %}
|
||||
|
||||
|
||||
@ -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 #}
|
||||
|
||||
@ -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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{# TAB 3 CONTENT: APPLICATION KPIS #}
|
||||
<div class="tab-pane fade" id="kpis" role="tabpanel" aria-labelledby="kpis-tab">
|
||||
<div class="row g-3 stats-grid">
|
||||
|
||||
{# 1. Job Avg. Score #}
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card text-center h-100 kpi-card">
|
||||
<div class="card-body p-2">
|
||||
<i class="fas fa-star text-primary mb-1 d-block" style="font-size: 1.2rem;"></i>
|
||||
<div class="h4 mb-0 text-primary fw-bold">{{ avg_match_score|floatformat:1 }}</div>
|
||||
<small class="text-muted d-block">{% trans "Avg. AI Score" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 2. High Potential Count #}
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card text-center h-100">
|
||||
<div class="card-body p-2">
|
||||
<i class="fas fa-trophy text-success mb-1 d-block" style="font-size: 1.2rem;"></i>
|
||||
<div class="h4 mb-0 text-success fw-bold">{{ high_potential_count }}</div>
|
||||
<small class="text-muted d-block">{% trans "High Potential" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 3. Avg. Time to Interview #}
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card text-center h-100">
|
||||
<div class="card-body p-2">
|
||||
<i class="fas fa-calendar-alt text-info mb-1 d-block" style="font-size: 1.2rem;"></i>
|
||||
<div class="h4 mb-0 text-info fw-bold">{{ avg_t2i_days|floatformat:1 }}d</div>
|
||||
<small class="text-muted d-block">{% trans "Time to Interview" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# 4. Avg. Exam Review Time #}
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card text-center h-100">
|
||||
<div class="card-body p-2">
|
||||
<i class="fas fa-hourglass-half text-secondary mb-1 d-block" style="font-size: 1.2rem;"></i>
|
||||
<div class="h4 mb-0 text-secondary fw-bold">{{ avg_t_in_exam_days|floatformat:1 }}d</div>
|
||||
<small class="text-muted d-block">{% trans "Avg. Exam Review" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-end text-muted small mt-3 me-2">
|
||||
<i class="fas fa-info-circle me-1"></i> {% trans "KPIs based on completed applicant data." %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</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>
|
||||
|
||||
<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;">
|
||||
{# REMOVED: Standalone Applicant Tracking Card (It is now in a tab) #}
|
||||
|
||||
<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,35 +536,7 @@
|
||||
{# 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">
|
||||
<i class="fas fa-user-plus"></i> {% trans "Create Applicant" %}
|
||||
@ -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 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>
|
||||
</div>
|
||||
|
||||
{# --- 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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<div class="join-url-container">
|
||||
<div id="copy-message" style="opacity: 0;">{% trans "Copied!" %}</div>
|
||||
|
||||
{% 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-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>
|
||||
|
||||
@ -547,6 +547,33 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" style="font-size: 0.9rem;">
|
||||
<span style="display: inline-flex; align-items: center;">
|
||||
<a href="{% url 'job_list' %}"
|
||||
style="color: white; text-decoration: none; padding-right: 8px;">
|
||||
JOBS
|
||||
</a>
|
||||
<span style="color: #6c757d; padding-right: 8px;">/</span>
|
||||
|
||||
<a href="{% url 'candidate_list' %}"
|
||||
style="color: white; text-decoration: none; padding-right: 8px;">
|
||||
CANDIDATES
|
||||
</a>
|
||||
<span style="color: #6c757d; padding-right: 8px;">/</span>
|
||||
|
||||
<a href="{% url 'candidate_detail' candidate.id %}"
|
||||
style="color: white; text-decoration: none; padding-right: 8px;">
|
||||
CANDIDATE
|
||||
</a>
|
||||
<span style="color: #6c757d; padding-right: 8px;">/</span>
|
||||
|
||||
<span style="color:gray; font-weight: 600;">
|
||||
RESUME Overview
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="score-box">
|
||||
<div class="score-value">{{ candidate.analysis_data.match_score|default:0 }}%</div>
|
||||
<div class="score-text">Match Score</div>
|
||||
|
||||
@ -151,6 +151,26 @@
|
||||
<canvas id="applicationsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-header">
|
||||
<h2 class="d-flex align-items-center mb-0">
|
||||
Select a job from the drop
|
||||
</h2>
|
||||
<form method="GET" action="{% url 'dashboard' %}">
|
||||
<select name="selected_job_id" onchange="this.form.submit()">
|
||||
<option value="">Show All</option>
|
||||
{% for job in jobs%}
|
||||
<option value="job" {% if request.GET.selected_job_id == selected_job_id %}selected{% endif %}>job</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
<div style="width: 75%; margin: auto;">
|
||||
<canvas id="job-doughnut-chart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
@ -218,6 +238,54 @@
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const applicant_stages=JSON.parse('{{applicant_stages|safe}}');
|
||||
const stage_counts=JSON.parse('{{stage_counts|safe}}');
|
||||
console.log(applicant_stages)
|
||||
console.log(stage_counts)
|
||||
|
||||
const job_data = {
|
||||
labels: applicant_stages,
|
||||
datasets: [{
|
||||
label: 'My Doughnut Dataset',
|
||||
data: stage_counts,
|
||||
backgroundColor: [ // Define colors for your slices
|
||||
'rgb(255, 99, 132)', // Red
|
||||
'rgb(54, 162, 235)', // Blue
|
||||
'rgb(255, 205, 86)', // Yellow
|
||||
'rgb(200,200,100)',
|
||||
'rgb(30,40,80)',
|
||||
],
|
||||
hoverOffset: 4
|
||||
}]
|
||||
};
|
||||
|
||||
const dnt_config = {
|
||||
// Set type to 'doughnut'
|
||||
type: 'doughnut',
|
||||
data: job_data,
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'top',
|
||||
},
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Django Data Doughnut Chart'
|
||||
}
|
||||
},
|
||||
// The 'cutout' option is what makes it a doughnut chart (defaults to '50%')
|
||||
// cutout: '50%',
|
||||
}
|
||||
};
|
||||
|
||||
// 4. Initialize and render the chart
|
||||
const ctx_dnt = document.getElementById('job-doughnut-chart').getContext('2d');
|
||||
new Chart(ctx_dnt, dnt_config);
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user