This commit is contained in:
Faheed 2025-10-22 13:13:03 +03:00
parent fec156fab2
commit f33cf97975
28 changed files with 753 additions and 870 deletions

View File

@ -135,9 +135,9 @@ WSGI_APPLICATION = 'NorahUniversity.wsgi.application'
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'norahuniversity',
'USER': 'norahuniversity',
'PASSWORD': 'norahuniversity',
'NAME': 'haikal_db',
'USER': 'faheed',
'PASSWORD': 'Faheed@215',
'HOST': '127.0.0.1',
'PORT': '5432',
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -273,21 +273,12 @@ def create_job(request):
if request.method == "POST":
form = JobPostingForm(
request.POST, is_anonymous_user=not request.user.is_authenticated
request.POST
)
# to check user is authenticated or not
if form.is_valid():
try:
job = form.save(commit=False)
if request.user.is_authenticated:
job.created_by = (
request.user.get_full_name() or request.user.username
)
else:
job.created_by = request.POST.get("created_by", "").strip()
if not job.created_by:
job.created_by = request.user.username
job.save()
job_apply_url_relative=reverse('job_detail_candidate',kwargs={'slug':job.slug})
job_apply_url_absolute=request.build_absolute_uri(job_apply_url_relative)
@ -302,7 +293,7 @@ def create_job(request):
else:
messages.error(request, f"Please correct the errors below.{form.errors}")
else:
form = JobPostingForm(is_anonymous_user=not request.user.is_authenticated)
form = JobPostingForm()
return render(request, "jobs/create_job.html", {"form": form})
@ -313,21 +304,11 @@ def edit_job(request, slug):
if request.method == "POST":
form = JobPostingForm(
request.POST,
instance=job,
is_anonymous_user=not request.user.is_authenticated,
instance=job
)
if form.is_valid():
try:
job = form.save(commit=False)
if request.user.is_authenticated:
job.created_by = (
request.user.get_full_name() or request.user.username
)
else:
job.created_by = request.POST.get("created_by", "").strip()
if not job.created_by:
job.created_by = "University Administrator"
job.save()
form.save()
messages.success(request, f'Job "{job.title}" updated successfully!')
return redirect("job_list")
except Exception as e:
@ -338,7 +319,7 @@ def edit_job(request, slug):
else:
job = get_object_or_404(JobPosting, slug=slug)
form = JobPostingForm(
instance=job, is_anonymous_user=not request.user.is_authenticated
instance=job
)
return render(request, "jobs/edit_job.html", {"form": form, "job": job})
@ -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

View File

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

View File

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

View File

@ -150,8 +150,113 @@
</div>
</div>
{# ================================================= #}
{# SECTION 2: JOB CONTENT (CKEDITOR 5 Fields) #}
{# SECTION 2: INTERNAL AND PROMOTION #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-tags"></i> {% trans "Internal & Promotion" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.position_number.id_for_label }}" class="form-label">{% trans "Position Number" %}</label>
{{ form.position_number }}
{% if form.position_number.errors %}<div class="text-danger small mt-1">{{ form.position_number.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">{% trans "Reports To" %}</label>
{{ form.reporting_to }}
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
{{ form.open_positions }}
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
{{ form.max_applications }}
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
</div>
</div>
<div class="col-12">
<div>
<label for="{{ form.hash_tags.id_for_label }}" class="form-label">{% trans "Hashtags (For Promotion/Search on Linkedin)" %}</label>
{{ form.hash_tags }}
{% if form.hash_tags.errors %}<div class="text-danger small mt-1">{{ form.hash_tags.errors }}</div>{% endif %}
<div class="form-text">{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}</div>
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 3: LOCATION AND DATES #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-map-marker-alt"></i> {% trans "Location, Dates, & Salary" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-4">
<div>
<label for="{{ form.location_city.id_for_label }}" class="form-label">{% trans "City" %}</label>
{{ form.location_city }}
{% if form.location_city.errors %}<div class="text-danger small mt-1">{{ form.location_city.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_state.id_for_label }}" class="form-label">{% trans "State/Province" %}</label>
{{ form.location_state }}
{% if form.location_state.errors %}<div class="text-danger small mt-1">{{ form.location_state.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_country.id_for_label }}" class="form-label">{% trans "Country" %}</label>
{{ form.location_country }}
{% if form.location_country.errors %}<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}<span class="text-danger">*</span></label>
{{ form.application_deadline }}
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.salary_range.id_for_label }}" class="form-label">{% trans "Salary Range" %}</label>
{{ form.salary_range }}
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 4: JOB CONTENT (CKEDITOR 5 Fields) #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
@ -179,21 +284,15 @@
</div>
{# ================================================= #}
{# SECTION 3: COMPENSATION AND APPLICATION #}
{# SECTION 5: APPLICATION Instructions #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-dollar-sign"></i> {% trans "Compensation & Application" %}</h5>
<h5><i class="fas fa-dollar-sign"></i> {% trans "Benefits & Application Instructions" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.salary_range.id_for_label }}" class="form-label">{% trans "Salary Range" %}</label>
{{ form.salary_range }}
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
</div>
</div>
{% comment %} (application_url comment removed for brevity) {% endcomment %}
<div class="col-12">
@ -215,117 +314,7 @@
</div>
</div>
{# ================================================= #}
{# SECTION 4: LOCATION AND DATES #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-map-marker-alt"></i> {% trans "Location, Dates, & Status" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-4">
<div>
<label for="{{ form.location_city.id_for_label }}" class="form-label">{% trans "City" %}</label>
{{ form.location_city }}
{% if form.location_city.errors %}<div class="text-danger small mt-1">{{ form.location_city.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_state.id_for_label }}" class="form-label">{% trans "State/Province" %}</label>
{{ form.location_state }}
{% if form.location_state.errors %}<div class="text-danger small mt-1">{{ form.location_state.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_country.id_for_label }}" class="form-label">{% trans "Country" %}</label>
{{ form.location_country }}
{% if form.location_country.errors %}<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}</label>
{{ form.application_deadline }}
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.application_start_date.id_for_label }}" class="form-label">{% trans "Application Start Date" %}</label>
{{ form.application_start_date }}
{% if form.application_start_date.errors %}<div class="text-danger small mt-1">{{ form.application_start_date.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.joining_date.id_for_label }}" class="form-label">{% trans "Desired Joining Date" %}</label>
{{ form.joining_date }}
{% if form.joining_date.errors %}<div class="text-danger small mt-1">{{ form.joining_date.errors }}</div>{% endif %}
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 5: INTERNAL AND PROMOTION #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-tags"></i> {% trans "Internal & Promotion" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.position_number.id_for_label }}" class="form-label">{% trans "Position Number" %}</label>
{{ form.position_number }}
{% if form.position_number.errors %}<div class="text-danger small mt-1">{{ form.position_number.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">{% trans "Reports To" %}</label>
{{ form.reporting_to }}
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
{{ form.open_positions }}
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
{{ form.max_applications }}
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.created_by.id_for_label }}" class="form-label">{% trans "Created By" %}</label>
{{ form.created_by }}
{% if form.created_by.errors %}<div class="text-danger small mt-1">{{ form.created_by.errors }}</div>{% endif %}
</div>
</div>
<div class="col-12">
<div>
<label for="{{ form.hash_tags.id_for_label }}" class="form-label">{% trans "Hashtags (For Promotion/Search)" %}</label>
{{ form.hash_tags }}
{% if form.hash_tags.errors %}<div class="text-danger small mt-1">{{ form.hash_tags.errors }}</div>{% endif %}
<div class="form-text">{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}</div>
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# ACTION BUTTONS #}

View File

@ -150,8 +150,113 @@
</div>
</div>
{# ================================================= #}
{# SECTION 2: JOB CONTENT (CKEDITOR 5 Fields) #}
{# SECTION 2: INTERNAL AND PROMOTION #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-tags"></i> {% trans "Internal & Promotion" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.position_number.id_for_label }}" class="form-label">{% trans "Position Number" %}</label>
{{ form.position_number }}
{% if form.position_number.errors %}<div class="text-danger small mt-1">{{ form.position_number.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">{% trans "Reports To" %}</label>
{{ form.reporting_to }}
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
{{ form.open_positions }}
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
{{ form.max_applications }}
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
</div>
</div>
<div class="col-12">
<div>
<label for="{{ form.hash_tags.id_for_label }}" class="form-label">{% trans "Hashtags (For Promotion/Search on Linkedin)" %}</label>
{{ form.hash_tags }}
{% if form.hash_tags.errors %}<div class="text-danger small mt-1">{{ form.hash_tags.errors }}</div>{% endif %}
<div class="form-text">{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}</div>
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 3: LOCATION AND DATES #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-map-marker-alt"></i> {% trans "Location, Dates, & Salary" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-4">
<div>
<label for="{{ form.location_city.id_for_label }}" class="form-label">{% trans "City" %}</label>
{{ form.location_city }}
{% if form.location_city.errors %}<div class="text-danger small mt-1">{{ form.location_city.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_state.id_for_label }}" class="form-label">{% trans "State/Province" %}</label>
{{ form.location_state }}
{% if form.location_state.errors %}<div class="text-danger small mt-1">{{ form.location_state.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_country.id_for_label }}" class="form-label">{% trans "Country" %}</label>
{{ form.location_country }}
{% if form.location_country.errors %}<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}<span class="text-danger">*</span></label>
{{ form.application_deadline }}
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.salary_range.id_for_label }}" class="form-label">{% trans "Salary Range" %}</label>
{{ form.salary_range }}
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 4: JOB CONTENT (CKEDITOR 5 Fields) #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
@ -179,21 +284,15 @@
</div>
{# ================================================= #}
{# SECTION 3: COMPENSATION AND APPLICATION #}
{# SECTION 5: APPLICATION Instructions #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-dollar-sign"></i> {% trans "Compensation & Application" %}</h5>
<h5><i class="fas fa-dollar-sign"></i> {% trans "Benefits & Application Instructions" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.salary_range.id_for_label }}" class="form-label">{% trans "Salary Range" %}</label>
{{ form.salary_range }}
{% if form.salary_range.errors %}<div class="text-danger small mt-1">{{ form.salary_range.errors }}</div>{% endif %}
</div>
</div>
{% comment %} (application_url comment removed for brevity) {% endcomment %}
<div class="col-12">
@ -215,117 +314,7 @@
</div>
</div>
{# ================================================= #}
{# SECTION 4: LOCATION AND DATES #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-map-marker-alt"></i> {% trans "Location, Dates, & Status" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-4">
<div>
<label for="{{ form.location_city.id_for_label }}" class="form-label">{% trans "City" %}</label>
{{ form.location_city }}
{% if form.location_city.errors %}<div class="text-danger small mt-1">{{ form.location_city.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_state.id_for_label }}" class="form-label">{% trans "State/Province" %}</label>
{{ form.location_state }}
{% if form.location_state.errors %}<div class="text-danger small mt-1">{{ form.location_state.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.location_country.id_for_label }}" class="form-label">{% trans "Country" %}</label>
{{ form.location_country }}
{% if form.location_country.errors %}<div class="text-danger small mt-1">{{ form.location_country.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.application_deadline.id_for_label }}" class="form-label">{% trans "Application Deadline" %}</label>
{{ form.application_deadline }}
{% if form.application_deadline.errors %}<div class="text-danger small mt-1">{{ form.application_deadline.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.application_start_date.id_for_label }}" class="form-label">{% trans "Application Start Date" %}</label>
{{ form.application_start_date }}
{% if form.application_start_date.errors %}<div class="text-danger small mt-1">{{ form.application_start_date.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.joining_date.id_for_label }}" class="form-label">{% trans "Desired Joining Date" %}</label>
{{ form.joining_date }}
{% if form.joining_date.errors %}<div class="text-danger small mt-1">{{ form.joining_date.errors }}</div>{% endif %}
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# SECTION 5: INTERNAL AND PROMOTION #}
{# ================================================= #}
<div class="card mb-4 shadow-sm">
<div class="card-header-themed">
<h5><i class="fas fa-tags"></i> {% trans "Internal & Promotion" %}</h5>
</div>
<div class="card-body">
<div class="row g-4">
<div class="col-md-6">
<div>
<label for="{{ form.position_number.id_for_label }}" class="form-label">{% trans "Position Number" %}</label>
{{ form.position_number }}
{% if form.position_number.errors %}<div class="text-danger small mt-1">{{ form.position_number.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-6">
<div>
<label for="{{ form.reporting_to.id_for_label }}" class="form-label">{% trans "Reports To" %}</label>
{{ form.reporting_to }}
{% if form.reporting_to.errors %}<div class="text-danger small mt-1">{{ form.reporting_to.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.open_positions.id_for_label }}" class="form-label">{% trans "Open Positions" %}</label>
{{ form.open_positions }}
{% if form.open_positions.errors %}<div class="text-danger small mt-1">{{ form.open_positions.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.max_applications.id_for_label }}" class="form-label">{% trans "Max Applications" %}</label>
{{ form.max_applications }}
{% if form.max_applications.errors %}<div class="text-danger small mt-1">{{ form.max_applications.errors }}</div>{% endif %}
</div>
</div>
<div class="col-md-4">
<div>
<label for="{{ form.created_by.id_for_label }}" class="form-label">{% trans "Created By" %}</label>
{{ form.created_by }}
{% if form.created_by.errors %}<div class="text-danger small mt-1">{{ form.created_by.errors }}</div>{% endif %}
</div>
</div>
<div class="col-12">
<div>
<label for="{{ form.hash_tags.id_for_label }}" class="form-label">{% trans "Hashtags (For Promotion/Search)" %}</label>
{{ form.hash_tags }}
{% if form.hash_tags.errors %}<div class="text-danger small mt-1">{{ form.hash_tags.errors }}</div>{% endif %}
<div class="form-text">{% trans "Comma-separated list of hashtags, e.g., #hiring, #professor" %}</div>
</div>
</div>
</div>
</div>
</div>
{# ================================================= #}
{# ACTION BUTTONS #}

View File

@ -1,4 +1,3 @@
{% extends "base.html" %}
{% load i18n static %}
@ -217,8 +216,17 @@
padding-top: 0.5rem;
}
/* Custom CSS for simplified stat card (from previous answer) */
.stats-grid .kpi-card {
border-left: 4px solid var(--kaauh-teal);
background-color: #f0faff;
}
.stats-grid .card-body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>
{% endblock %}
@ -236,7 +244,7 @@
<div class="row g-4">
{# LEFT COLUMN: JOB DETAILS WITH TABS #}
<div class="col-lg-8">
<div class="col-lg-7">
<div class="card shadow-sm no-hover">
{# HEADER SECTION #}
@ -248,6 +256,7 @@
<div class="d-flex align-items-center gap-2">
<span class="badge status-badge">
{# Corrected status badge logic to close the span correctly #}
{% if job.status == "ACTIVE" %}
<span class="badge bg-success status-badge">
{% elif job.status == "DRAFT" %}
@ -262,6 +271,7 @@
<span class="badge bg-secondary status-badge">
{% endif %}
{{ job.get_status_display }}
</span>
<button type="button" class="btn btn-outline-light btn-sm ms-2" data-bs-toggle="modal" data-bs-target="#editStatusModal">
<i class="fas fa-edit text-primary"></i>
</button>
@ -281,13 +291,12 @@
<i class="fas fa-file-alt me-1"></i> {% trans "Description & Requirements" %}
</button>
</li>
{% if job.application_instructions %}
<li class="nav-item" role="presentation">
<button class="nav-link" id="instructions-tab" data-bs-toggle="tab" data-bs-target="#instructions" type="button" role="tab" aria-controls="instructions" aria-selected="false">
<i class="fas fa-paper-plane me-1"></i> {% trans "Application" %}
<button class="nav-link" id="kpis-tab" data-bs-toggle="tab" data-bs-target="#kpis" type="button" role="tab" aria-controls="kpis" aria-selected="false">
<i class="fas fa-chart-line me-1"></i> {% trans "Application KPIs" %}
</button>
</li>
{% endif %}
</ul>
<div class="card-body">
@ -323,21 +332,20 @@
</div>
<div class="col-md-4">
<button
type="button"
class="btn btn-main-action btn-sm"
id="copyJobLinkButton"
data-url="{{ job.application_url }}">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6 heroicon">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 0 0 2.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 0 0-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25ZM6.75 12h.008v.008H6.75V12Zm0 3h.008v.008H6.75V15Zm0 3h.008v.008H6.75V18Z" />
</svg>
{% trans "Share Public Link" %}
</button>
<button
type="button"
class="btn btn-main-action btn-sm"
id="copyJobLinkButton"
data-url="{{ job.application_url }}">
{# Replaced bulky SVG with simpler Font Awesome icon #}
<i class="fas fa-link"></i>
{% trans "Share Public Link" %}
</button>
<span id="copyFeedback" class="text-success ms-2 small" style="display:none;">
{% trans "Copied!" %}
</span>
</div>
<span id="copyFeedback" class="text-success ms-2 small" style="display:none;">
{% trans "Copied!" %}
</span>
</div>
</div>
<h5 class="text-muted mb-3">{% trans "Financial & Timeline" %}</h5>
@ -392,19 +400,74 @@
<div class="text-secondary">{{ job.benefits|safe}}</div>
</div>
{% endif %}
</div>
{# TAB 3 CONTENT: APPLICATION INSTRUCTIONS #}
{% if job.application_instructions %}
<div class="tab-pane fade" id="instructions" role="tabpanel" aria-labelledby="instructions-tab">
<div class="mb-4">
{% if job.application_instructions %}
<div class="mb-4">
<h5>{% trans "Application Instructions" %}</h5>
<div class="text-secondary">{{ job.application_instructions|safe }}</div>
</div>
</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 %}

View File

@ -278,7 +278,7 @@
</tr>
<tr class="nested-metrics-row">
<th style="width: calc(50% / 7);">{% trans "All Applicants" %}</th>
<th style="width: calc(50% / 7);">{% trans "All" %}</th>
<th style="width: calc(50% / 7);">{% trans "Screened" %}</th>
<th style="width: calc(50% / 7 * 2);">{% trans "Exam" %}</th>
<th style="width: calc(50% / 7 * 2);">{% trans "Interview" %}</th>
@ -386,7 +386,7 @@
{# --- END OF JOB LIST CONTAINER --- #}
{% include "includes/paginator.html" %}
{% if not jobs and not job_list_data and not page_obj %}
<div class="text-center py-5 card shadow-sm">
<div class="text-center py-5 card shadow-sm mt-4">
<div class="card-body">
<i class="fas fa-briefcase fa-3x mb-3" style="color: var(--kaauh-teal-dark);"></i>
<h3>{% trans "No job postings found" %}</h3>

View File

@ -1,10 +1,9 @@
{% extends 'base.html' %}
{% load static i18n %}
{% block customCSS %}
<style>
/* -------------------------------------------------------------------------- */
/* KAAT-S Redesign CSS */
/* KAAT-S Redesign CSS - Optimized Compact Detail View (Settings Removed) */
/* -------------------------------------------------------------------------- */
:root {
@ -20,93 +19,56 @@
}
body {
background-color: #f0f2f5; /* Off-white page background */
font-family: 'Inter', sans-serif; /* Use a modern font stack */
background-color: #f0f2f5;
font-family: 'Inter', sans-serif;
}
/* ------------------ General Layout & Card Styles ------------------ */
.container {
width:auto;
padding: 3rem 1.5rem;
}
.card {
border: none; /* Remove default border */
border: none;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.08), 0 4px 10px rgba(0,0,0,0.05); /* Deep, soft shadow */
background-color: white;
margin-bottom: 2.5rem;
transition: all 0.3s ease;
box-shadow: 0 5px 15px rgba(0,0,0,0.05), 0 2px 5px rgba(0,0,0,0.03);
margin-bottom: 1.5rem;
transition: all 0.2s ease;
}
.card:not(.no-hover):hover {
transform: translateY(-3px);
box-shadow: 0 15px 40px rgba(0,0,0,0.1), 0 6px 15px rgba(0,0,0,0.08);
}
.card.no-hover:hover {
transform: none;
box-shadow: 0 10px 30px rgba(0,0,0,0.08), 0 4px 10px rgba(0,0,0,0.05);
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0,0,0,0.08);
}
.card-body {
padding: 2rem;
padding: 1.5rem;
}
/* ------------------ Header & Title Styles ------------------ */
/* ------------------ Main Header & Title Styles ------------------ */
.card-header {
background-color: var(--kaauh-gray-light);
border-bottom: 1px solid var(--kaauh-border);
padding: 2rem;
.main-title-card {
padding: 1.5rem 2rem;
background-color: white;
border-bottom: 3px solid var(--kaauh-teal);
border-radius: 12px 12px 0 0;
display: flex;
justify-content: space-between;
align-items: flex-start; /* Align title group to the top */
flex-wrap: wrap;
}
.card-header-title-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.card-header h1 {
color: var(--kaauh-teal-dark);
font-weight: 800; /* Extra bold for prominence */
margin: 0;
display: flex;
align-items: center;
gap: 1rem;
font-size: 2.5rem;
}
.card-header .heroicon {
width: 2.5rem;
height: 2.5rem;
color: var(--kaauh-teal);
}
.card-header .btn-secondary-back {
/* Subtle Back Button */
align-self: flex-start;
background-color: transparent;
border: none;
color: var(--kaauh-secondary-text);
font-weight: 600;
font-size: 1rem;
padding: 0.5rem 0.75rem;
transition: color 0.2s;
}
.card-header .btn-secondary-back:hover {
color: var(--kaauh-teal);
text-decoration: underline;
}
/* ------------------ Status Badge Styles ------------------ */
.main-title-card h1 {
color: var(--kaauh-teal-dark);
font-weight: 800;
margin: 0;
font-size: 2rem;
}
.main-title-card .heroicon {
width: 2rem;
height: 2rem;
color: var(--kaauh-teal);
}
.status-badge {
font-size: 0.85rem;
padding: 0.5em 1em;
border-radius: 20px; /* Pill shape */
font-size: 0.75rem;
padding: 0.35em 0.8em;
border-radius: 15px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-top: 0.5rem;
}
.bg-waiting { background-color: #ffc107 !important; color: var(--kaauh-primary-text) !important;}
.bg-started { background-color: var(--kaauh-teal) !important; color: white !important;}
@ -114,13 +76,13 @@ body {
/* ------------------ Detail Row & Content Styles ------------------ */
.card h2 {
.detail-section h2 {
color: var(--kaauh-teal-dark);
font-weight: 700;
padding: 1.5rem 2rem 1rem;
margin: 0;
font-size: 1.5rem;
border-bottom: 1px solid var(--kaauh-border);
font-size: 1.25rem;
margin-bottom: 1rem;
border-bottom: 2px solid var(--kaauh-teal-light);
padding-bottom: 0.5rem;
}
.detail-row-group {
@ -128,10 +90,10 @@ body {
}
.detail-row {
display: grid;
grid-template-columns: minmax(150px, 40%) 1fr;
padding: 1rem 2rem;
border-bottom: 1px solid var(--kaauh-border);
display: flex;
justify-content: space-between;
padding: 0.75rem 0;
border-bottom: 1px dashed var(--kaauh-border);
align-items: center;
}
.detail-row:last-child {
@ -139,273 +101,194 @@ body {
}
.detail-label {
font-weight: 600;
color: var(--kaauh-teal-dark);
text-align: left;
font-size: 0.95rem;
color: var(--kaauh-teal);
font-size: 0.9rem;
flex-basis: 45%;
}
.detail-value {
text-align: right;
color: var(--kaauh-primary-text);
word-wrap: break-word;
font-weight: 500;
text-align: right;
font-size: 0.9rem;
flex-basis: 55%;
}
/* ------------------ Join Info & Copy Button ------------------ */
.join-info-card .card-body {
padding-top: 2rem;
.join-info-card {
border-left: 5px solid var(--kaauh-teal); /* Highlight join info */
}
.btn-primary {
background-color: var(--kaauh-teal);
border-color: var(--kaauh-teal);
color: white;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 8px;
transition: all 0.2s ease;
padding: 0.6rem 1.25rem;
border-radius: 6px;
}
.btn-primary:hover {
background-color: var(--kaauh-teal-dark);
border-color: var(--kaauh-teal-dark);
box-shadow: 0 4px 8px rgba(0, 99, 110, 0.3);
}
.join-url-container {
display: flex;
gap: 1rem;
align-items: center;
margin-top: 1.5rem;
position: relative;
padding: 1rem 0; /* Add padding for clear space around the copy area */
margin-top: 1rem;
}
.join-url-display {
flex-grow: 1;
background-color: var(--kaauh-gray-light);
border: 1px solid var(--kaauh-border);
border-radius: 8px;
padding: 0.75rem 1rem;
word-break: break-all;
font-size: 0.9rem;
color: var(--kaauh-secondary-text);
font-family: monospace; /* Monospace for links/code */
padding: 0.5rem 0.75rem;
font-size: 0.85rem;
}
.join-url-display strong {
color: var(--kaauh-teal-dark);
font-family: 'Inter', sans-serif;
}
.btn-copy {
flex-shrink: 0;
background-color: var(--kaauh-teal-dark); /* Darker teal for a clean utility look */
border: none;
color: white;
padding: 0.75rem 1rem;
border-radius: 8px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.btn-copy:hover {
background-color: var(--kaauh-teal);
}
.btn-copy i {
margin-right: 0.25rem;
}
/* 🎯 Copy Message Pill Style */
#copy-message {
position: absolute;
top: -5px;
right: 0;
background-color: var(--kaauh-success);
color: white;
padding: 0.2rem 0.6rem;
border-radius: 20px; /* Pill shape */
opacity: 0;
transition: opacity 0.4s ease-in-out;
z-index: 10;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.5px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
padding: 0.5rem 0.75rem;
background-color: var(--kaauh-teal-dark);
}
/* ------------------ Footer & Actions ------------------ */
.card-footer {
.action-bar-footer {
border-top: 1px solid var(--kaauh-border);
padding: 1.5rem 2rem;
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: flex-start;
padding: 1rem 1.5rem;
gap: 0.75rem;
background-color: var(--kaauh-gray-light);
border-radius: 0 0 12px 12px;
}
.btn-danger {
background-color: var(--kaauh-danger);
border-color: var(--kaauh-danger);
color: white;
.btn-footer-action {
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.btn-danger:hover {
background-color: #c82333;
border-color: #bd2130;
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
padding: 0.5rem 1rem;
border-radius: 6px;
font-size: 0.9rem;
}
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
color: white;
font-weight: 600;
padding: 0.75rem 1.5rem;
border-radius: 8px;
transition: all 0.2s ease;
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #545b62;
}
/* ------------------ API Response Styling ------------------ */
#gateway-response-card {
border-left: 5px solid var(--kaauh-teal); /* Prominent left border */
}
#gateway-response-card .card-body {
padding: 1.5rem;
}
#gateway-response-card h3 {
/* ------------------ Comments Section ------------------ */
#comments-card .card-header {
background-color: var(--kaauh-teal-light);
color: var(--kaauh-teal-dark);
font-weight: 700;
font-size: 1.35rem;
margin-bottom: 1rem;
}
#gateway-response-card pre {
background-color: #fff;
border: 1px solid var(--kaauh-border);
border-radius: 8px;
padding: 1rem;
font-size: 0.8rem;
color: var(--kaauh-primary-text);
white-space: pre-wrap;
word-wrap: break-word;
padding: 1rem 1.5rem;
font-weight: 600;
border-radius: 12px 12px 0 0;
}
/* Comment card body/item styling is kept compact */
</style>
{% endblock %}
{% block content %}
<div class="container">
<div class="container-fluid">
<div class="card no-hover">
<div class="card-header">
<div class="card-header-title-group">
<h1>
<svg class="heroicon" viewBox="0 0 24 24" fill="none" xmlns="[http://www.w3.org/2000/svg](http://www.w3.org/2000/svg)">
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0 8.268-2.943-9.542-7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{{ meeting.topic }}
</h1>
<div class="col-auto">
<span class="status-badge bg-{{ meeting.status }}">
{{ meeting.status|title }}
</span>
{# --- TOP BAR / BACK BUTTON --- #}
<div class="d-flex justify-content-between align-items-center mb-3">
<a href="{% url 'list_meetings' %}" class="btn btn-secondary-back">
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Meetings" %}
</a>
</div>
<div class="row g-4">
{# --- LEFT COLUMN (MAIN DETAILS) --- #}
<div class="col-lg-6">
<div class="card no-hover h-100">
{# --- CONSOLIDATED HEADER --- #}
<div class="main-title-card">
<div class="d-flex justify-content-between align-items-start">
<div class="card-header-title-group">
<h1 class="mb-1">
<svg class="heroicon me-2" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0 8.268-2.943-9.542-7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
{{ meeting.topic }}
</h1>
<div class="d-flex align-items-center gap-3">
<span class="status-badge bg-{{ meeting.status }}">
{{ meeting.status|title }}
</span>
{% if meeting.interview %}
<span class="text-muted small">
{% trans "Candidate" %}: <a class="text-primary-theme fw-bold text-decoration-none" href="{% url 'candidate_detail' meeting.interview.candidate.slug %}">{{ meeting.interview.candidate.name }} </a>
</span>
{% endif %}
</div>
</div>
</div>
</div>
{% if meeting.interview %}
<div class="col-auto">
<span class="status-badge">
Candidate Name : <a class="text-primary-theme" href="{% url 'candidate_detail' meeting.interview.candidate.slug %}">{{ meeting.interview.candidate.name }} </a>
</span>
{# --- MAIN DETAIL BODY --- #}
<div class="card-body detail-section">
<h2>{% trans "Core Details" %}</h2>
<div class="detail-row-group">
<div class="detail-row"><div class="detail-label">{% trans "Meeting ID" %}:</div><div class="detail-value">{{ meeting.meeting_id }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Start Time" %}:</div><div class="detail-value">{{ meeting.start_time|date:"M d, Y H:i" }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Duration" %}:</div><div class="detail-value">{{ meeting.duration }} {% trans "minutes" %}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Timezone" %}:</div><div class="detail-value">{{ meeting.timezone|default:"UTC" }}</div></div>
<div class="detail-row"><div class="detail-label">{% trans "Host Email" %}:</div><div class="detail-value">{{ meeting.host_email|default:"N/A" }}</div></div>
</div>
{% endif %}
</div>
<a href="{% url 'list_meetings' %}" class="btn btn-secondary-back">
<i class="fas fa-arrow-left"></i> {% trans "Back to Meetings" %}
</a>
</div>
</div>
<div 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 %}

View File

@ -294,15 +294,15 @@
<div class="tab-pane fade" id="resume-pane" role="tabpanel" aria-labelledby="resume-tab">
<h5 class="text-primary mb-4">{% trans "Resume Document" %}</h5>
<div class="d-flex align-items-center justify-content-between p-3 border rounded">
<div>
{% comment %} <div>
<p class="mb-1"><strong>{{ candidate.resume.name }}</strong></p>
<small class="text-muted">{{ candidate.resume.name|truncatechars:30 }}</small>
</div>
</div> {% endcomment %}
<div class="d-flex flex-column gap-2">
<div class="d-flex gap-2">
<a href="{{ candidate.resume.url }}" target="_blank" class="btn btn-outline-primary">
<i class="fas fa-eye me-1"></i>
{% trans "View Resume" %}
{% trans "View Actual Resume" %}
</a>
<a href="{{ candidate.resume.url }}" download class="btn btn-main-action">
<i class="fas fa-download me-1"></i>
@ -311,7 +311,7 @@
</div>
<a href="{% url 'candidate_resume_template' candidate.slug %}" class="btn btn-outline-info">
<i class="fas fa-file-alt me-1"></i>
{% trans "View Formatted Resume" %}
{% trans "View Resume AI Overview" %}
</a>
</div>
</div>

View File

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

View File

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