new chnages #21

Merged
ismail merged 1 commits from frontend into main 2025-10-23 18:54:18 +03:00
67 changed files with 237705 additions and 132 deletions

View File

@ -155,19 +155,27 @@ DATABASES = {
# AUTH_PASSWORD_VALIDATORS = [
# {
# 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
# },
# {
# 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
# },
# {
# 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
# },
# {
# 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
# },
# ]
# settings.py
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{ {
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
}, },
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
] ]

View File

@ -1,3 +1,7 @@
from pathlib import Path
from rich import print
from django.conf import settings
import os
import uuid import uuid
import random import random
from datetime import date, timedelta from datetime import date, timedelta
@ -39,9 +43,9 @@ class Command(BaseCommand):
self.stdout.write(f"Preparing to create {jobs_count} jobs and {candidates_count} candidates.") self.stdout.write(f"Preparing to create {jobs_count} jobs and {candidates_count} candidates.")
# 1. Clear existing data (Optional, but useful for clean seeding) # 1. Clear existing data (Optional, but useful for clean seeding)
Candidate.objects.all().delete()
JobPosting.objects.all().delete() JobPosting.objects.all().delete()
Source.objects.all().delete() FormTemplate.objects.all().delete()
Candidate.objects.all().delete()
self.stdout.write(self.style.WARNING("Existing JobPostings and Candidates cleared.")) self.stdout.write(self.style.WARNING("Existing JobPostings and Candidates cleared."))
# 2. Create Foreign Key dependency: Source # 2. Create Foreign Key dependency: Source
@ -74,8 +78,7 @@ class Command(BaseCommand):
# Random dates # Random dates
start_date = fake.date_object() start_date = fake.date_object()
deadline_date = start_date + timedelta(days=random.randint(14, 60)) deadline_date = start_date + timedelta(days=random.randint(14, 60))
joining_date = deadline_date + timedelta(days=random.randint(30, 90))
# Use Faker's HTML generation for CKEditor5 fields # Use Faker's HTML generation for CKEditor5 fields
description_html = f"<h1>{title} Role</h1>" + "".join(f"<p>{fake.paragraph(nb_sentences=3, variable_nb_sentences=True)}</p>" for _ in range(3)) description_html = f"<h1>{title} Role</h1>" + "".join(f"<p>{fake.paragraph(nb_sentences=3, variable_nb_sentences=True)}</p>" for _ in range(3))
@ -88,26 +91,11 @@ class Command(BaseCommand):
"department": department, "department": department,
"job_type": job_type, "job_type": job_type,
"workplace_type": random.choice(WORKPLACE_TYPES), "workplace_type": random.choice(WORKPLACE_TYPES),
"location_city": fake.city(),
"location_state": fake.state_abbr(),
"location_country": "Saudia Arabia", "location_country": "Saudia Arabia",
"description": description_html, "description": description_html,
"qualifications": qualifications_html, "qualifications": qualifications_html,
"salary_range": salary_range,
"benefits": benefits_html,
"application_url": fake.url(),
"application_start_date": start_date,
"application_deadline": deadline_date, "application_deadline": deadline_date,
"application_instructions": instructions_html,
"created_by": "Faker Script",
"status": random.choice(STATUS_CHOICES), "status": random.choice(STATUS_CHOICES),
"hash_tags": f"#{department.lower().replace(' ', '')},#jobopening,#{fake.word()}",
"position_number": f"{department[:3].upper()}{random.randint(100, 999)}",
"reporting_to": random.choice(REPORTING_TO),
"joining_date": joining_date,
"open_positions": random.randint(1, 5),
"source": default_source,
"published_at": timezone.now() if random.random() < 0.7 else None,
} }
job = JobPosting.objects.create( job = JobPosting.objects.create(
@ -123,28 +111,33 @@ class Command(BaseCommand):
for i in range(candidates_count): for i in range(candidates_count):
# Link candidate to a random job # Link candidate to a random job
target_job = random.choice(created_jobs) target_job = random.choice(created_jobs)
print(target_job)
first_name = fake.first_name() first_name = fake.first_name()
last_name = fake.last_name() last_name = fake.last_name()
path = os.path.join(settings.BASE_DIR,'media/resumes/')
# path = Path('media/resumes/') # <-- CORRECT
file = random.choice(os.listdir(path))
print(file)
# file = os.path.abspath(file)
candidate_data = { candidate_data = {
"first_name": first_name, "first_name": first_name,
"last_name": last_name, "last_name": last_name,
# Create a plausible email based on name # Create a plausible email based on name
"email": f"{first_name.lower()}.{last_name.lower()}@{fake.domain_name()}", "email": f"{first_name.lower()}.{last_name.lower()}@{fake.domain_name()}",
"phone": fake.phone_number(), "phone": "0566987458",
"address": fake.address(), "address": fake.address(),
# Placeholder resume path # Placeholder resume path
'match_score': random.randint(0, 100), "resume": 'resumes/'+ file,
"resume": f"resumes/{last_name.lower()}_{target_job.internal_job_id}_{fake.file_name(extension='pdf')}",
"job": target_job, "job": target_job,
} }
print(candidate_data)
Candidate.objects.create(**candidate_data) Candidate.objects.create(**candidate_data)
self.stdout.write(self.style.NOTICE( self.stdout.write(self.style.NOTICE(
f'Created Candidate {i+1}/{candidates_count}: {first_name} for {target_job.title[:30]}...' f'Created Candidate {i+1}/{candidates_count}: {first_name} for {target_job.title[:30]}...'
)) ))
print("done")
else: else:
self.stdout.write(self.style.WARNING("No jobs created, skipping candidate generation.")) self.stdout.write(self.style.WARNING("No jobs created, skipping candidate generation."))

View File

@ -1,4 +1,4 @@
# Generated by Django 5.2.7 on 2025-10-22 16:33 # Generated by Django 5.2.7 on 2025-10-23 14:08
import django.core.validators import django.core.validators
import django.db.models.deletion import django.db.models.deletion
@ -221,6 +221,7 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='JobPosting', name='JobPosting',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')), ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated at')),
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')), ('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
@ -238,7 +239,7 @@ class Migration(migrations.Migration):
('application_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])), ('application_url', models.URLField(blank=True, help_text='URL where candidates apply', null=True, validators=[django.core.validators.URLValidator()])),
('application_deadline', models.DateField(db_index=True)), ('application_deadline', models.DateField(db_index=True)),
('application_instructions', django_ckeditor_5.fields.CKEditor5Field(blank=True, null=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)), ('internal_job_id', models.CharField(editable=False, max_length=50)),
('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)), ('created_by', models.CharField(blank=True, help_text='Name of person who created this job', max_length=100)),
('status', models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], db_index=True, default='DRAFT', max_length=20)), ('status', models.CharField(choices=[('DRAFT', 'Draft'), ('ACTIVE', 'Active'), ('CLOSED', 'Closed'), ('CANCELLED', 'Cancelled'), ('ARCHIVED', 'Archived')], db_index=True, default='DRAFT', max_length=20)),
('hash_tags', models.CharField(blank=True, help_text='Comma-separated hashtags for linkedin post like #hiring,#jobopening', max_length=200, validators=[recruitment.validators.validate_hash_tags])), ('hash_tags', models.CharField(blank=True, help_text='Comma-separated hashtags for linkedin post like #hiring,#jobopening', max_length=200, validators=[recruitment.validators.validate_hash_tags])),

View File

@ -92,7 +92,7 @@ class JobPosting(Base):
) )
# Internal Tracking # Internal Tracking
internal_job_id = models.CharField(max_length=50, primary_key=True, editable=False) internal_job_id = models.CharField(max_length=50, editable=False)
created_by = models.CharField( created_by = models.CharField(
max_length=100, blank=True, help_text="Name of person who created this job" max_length=100, blank=True, help_text="Name of person who created this job"
) )
@ -193,26 +193,29 @@ class JobPosting(Base):
return self.source.name if self.source else "System" return self.source.name if self.source else "System"
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
# from django.db import transaction
# Generate unique internal job ID if not exists # Generate unique internal job ID if not exists
if not self.internal_job_id: # with transaction.atomic():
prefix = "KAAUH" # if not self.internal_job_id:
year = timezone.now().year # prefix = "KAAUH"
# Get next sequential number # year = timezone.now().year
last_job = ( # # Get next sequential number
JobPosting.objects.filter( # last_job = (
internal_job_id__startswith=f"{prefix}-{year}-" # JobPosting.objects.select_for_update().filter(
) # internal_job_id__startswith=f"{prefix}-{year}-"
.order_by("internal_job_id") # )
.last() # .order_by("internal_job_id")
) # .last()
# )
if last_job: # if last_job:
last_num = int(last_job.internal_job_id.split("-")[-1]) # last_num = int(last_job.internal_job_id.split("-")[-1])
next_num = last_num + 1 # next_num = last_num + 1
else: # else:
next_num = 1 # next_num = 1
self.internal_job_id = f"{prefix}-{year}-{next_num:06d}" # self.internal_job_id = f"{prefix}-{year}-{next_num:06d}"
super().save(*args, **kwargs) super().save(*args, **kwargs)

View File

@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
@receiver(post_save, sender=JobPosting) @receiver(post_save, sender=JobPosting)
def format_job(sender, instance, created, **kwargs): def format_job(sender, instance, created, **kwargs):
if created: if created:
FormTemplate.objects.create(job=instance, is_active=True, name=instance.title) # FormTemplate.objects.create(job=instance, is_active=False, name=instance.title)
async_task( async_task(
'recruitment.tasks.format_job_description', 'recruitment.tasks.format_job_description',
instance.pk, instance.pk,

View File

@ -290,7 +290,7 @@ def handle_reume_parsing_and_scoring(pk):
"strengths": "Brief summary of strengths", "strengths": "Brief summary of strengths",
"weaknesses": "Brief summary of weaknesses", "weaknesses": "Brief summary of weaknesses",
"years_of_experience": "Total years of experience (float, e.g., 6.5)", "years_of_experience": "Total years of experience (float, e.g., 6.5)",
"criteria_checklist": {{ "Python": "Met", "AWS": "Not Mentioned"}}, "criteria_checklist": List of job requirements if any {{ "Python": "Met", "AWS": "Not Met"}} only output the criteria_checklist in one of ('Met','Not Met') don't output any extra text,
"category": "Most fitting professional field (e.g., Data Science), only output the category name and no other text example ('Software Development', 'correct') , ('Software Development and devops','wrong') ('Software Development / Backend Development','wrong')", "category": "Most fitting professional field (e.g., Data Science), only output the category name and no other text example ('Software Development', 'correct') , ('Software Development and devops','wrong') ('Software Development / Backend Development','wrong')",
"most_recent_job_title": "Candidate's most recent job title", "most_recent_job_title": "Candidate's most recent job title",
"recommendation": "Detailed hiring recommendation narrative", "recommendation": "Detailed hiring recommendation narrative",

BIN
resumes/AltaCV_Template.pdf Normal file

Binary file not shown.

BIN
resumes/Balance Sheet.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resumes/FaheeedResume.docx Normal file

Binary file not shown.

BIN
resumes/FaheeedResume.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resumes/cv-for-freshers.pdf Normal file

Binary file not shown.

BIN
resumes/cv-template.docx Normal file

Binary file not shown.

BIN
resumes/cv.pdf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
resumes/hassan-razas-cv.pdf Normal file

Binary file not shown.

Binary file not shown.

BIN
resumes/jitendra.pdf Normal file

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.

BIN
resumes/npu-cv.pdf Normal file

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.

View File

@ -111,12 +111,31 @@
{# ------------------- ADD EMAIL FORM ------------------- #} {# ------------------- ADD EMAIL FORM ------------------- #}
<h5 class="fw-bold mb-3">{% translate "Add Email Address" %}</h5> <h5 class="fw-bold mb-3">{% translate "Add Email Address" %}</h5>
<form method="post" action="{% url 'account_email' %}"> <form method="post" action="{% url 'account_email' %}">
{% csrf_token %} {% csrf_token %}
{{ form|crispy }}
{# Teal/Dark Green button consistent with "Save Changes" #} {# 1. Explicitly render non-field errors first #}
<button class="btn btn-success mt-3" type="submit" style="background-color: #008080; border-color: #008080;">{% translate "Add Email" %}</button> {% if form.non_field_errors %}
</form> <div class="alert alert-danger" role="alert">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{# 2. Render the fields using crispy #}
{{ form|crispy }}
{# 3. If there are any global errors (not common here, but safe to include) #}
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} mt-3">{{ message }}</div>
{% endfor %}
{% endif %}
{# Teal/Dark Green button consistent with "Save Changes" #}
<button class="btn btn-success mt-3" type="submit" style="background-color: #008080; border-color: #008080;">{% translate "Add Email" %}</button>
</form>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,6 +2,8 @@
{% load static %} {% load static %}
{% autoescape off %} {% autoescape off %}
{# Use the built-in context variable 'password_reset_url' that allauth provides. #}
<div style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: auto; border: 1px solid #ddd; border-radius: 8px; overflow: hidden;"> <div style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: auto; border: 1px solid #ddd; border-radius: 8px; overflow: hidden;">
<div style="background-color: #00636e; padding: 20px; color: white; text-align: center;"> <div style="background-color: #00636e; padding: 20px; color: white; text-align: center;">

View File

@ -1,5 +1,5 @@
{% load static i18n %} {% load static i18n %}
<!DOCTYPE html> {% load crispy_forms_tags %} <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@ -82,52 +82,8 @@
<form method="post" action="."> <form method="post" action=".">
{% csrf_token %} {% csrf_token %}
{# Non-Field Errors (General errors like tokens or passwords not matching) #} {# RENDER THE FORM USING CRISPY FORMS #}
{% if form.non_field_errors %} {{ form|crispy }}
<div class="alert alert-danger" role="alert">
{% for error in form.non_field_errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
{# Password 1 Field #}
<div class="mb-3">
<label for="{{ form.password.id_for_label }}" class="form-label fw-semibold">{% trans "New Password" %} *</label>
{# **CRITICAL FIX:** Iterate over the errors to display them correctly #}
{% if form.password.errors %}
<div class="alert alert-danger p-2 small">
{% for error in form.password.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<input type="password" name="{{ form.password.name }}" id="{{ form.password.id_for_label }}"
class="form-control" placeholder="{% trans 'Enter new password' %}" required autofocus>
</div>
{# Password 2 Field #}
<div class="mb-3">
<label for="{{ form.password2.id_for_label }}" class="form-label fw-semibold">{% trans "Confirm New Password" %} *</label>
{# **CRITICAL FIX:** Iterate over the errors to display them correctly #}
{% if form.password2.errors %}
<div class="alert alert-danger p-2 small">
{% for error in form.password2.errors %}
{{ error }}
{% endfor %}
</div>
{% endif %}
<input type="password" name="{{ form.password2.name }}" id="{{ form.password2.id_for_label }}"
class="form-control" placeholder="{% trans 'Confirm new password' %}" required>
</div>
{# Hidden fields MUST be present for the POST request to be valid #}
{{ form.uid }}
{{ form.token }}
{# Submit Button #} {# Submit Button #}
<button type="submit" class="btn btn-primary w-100 mt-4"> <button type="submit" class="btn btn-primary w-100 mt-4">

View File

@ -146,9 +146,12 @@
<div class="container-fluid py-4"> <div class="container-fluid py-4">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'job_detail' submission.template.job.slug %}">Job Detail</a></li> <li class="breadcrumb-item text-decorat"><a href="{% url 'job_detail' submission.template.job.slug %}" class="text-secondary">Job Detail</a></li>
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}">Form Template</a></li> <li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}" class="text-secondary">Form Template</a></li>
<li class="breadcrumb-item active" aria-current="page">Submission Details</li> <li class="breadcrumb-item active" aria-current="page" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">Submission Details</li>
</ol> </ol>
</nav> </nav>
<div class="row"> <div class="row">

View File

@ -185,9 +185,12 @@
<div class="container py-4"> <div class="container py-4">
<nav aria-label="breadcrumb"> <nav aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}">{% trans "Dashboard" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">{% trans "Dashboard" %}</a></li>
<li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}">{% trans "Form Templates" %}</a></li> <li class="breadcrumb-item"><a href="{% url 'form_templates_list' %}" class="text-secondary">{% trans "Form Templates" %}</a></li>
<li class="breadcrumb-item active">{% trans "Submissions" %}</li> <li class="breadcrumb-item active" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">{% trans "Submissions" %}</li>
</ol> </ol>
</nav> </nav>

View File

@ -364,6 +364,7 @@
</style> </style>
</head> </head>
<body> <body>
<div class="resume-container"> <div class="resume-container">
<div class="header"> <div class="header">
<div class="header-content"> <div class="header-content">

View File

@ -152,7 +152,10 @@
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li> <li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_list' %}" class="text-secondary">Jobs</a></li> <li class="breadcrumb-item"><a href="{% url 'job_list' %}" class="text-secondary">Jobs</a></li>
<li class="breadcrumb-item active" aria-current="page">Job Detail</li> <li class="breadcrumb-item active" aria-current="page" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">Job Detail</li>
</ol> </ol>
</nav> </nav>
<div class="row g-4"> <div class="row g-4">

View File

@ -186,7 +186,10 @@
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li> <li class="breadcrumb-item"><a href="{% url 'dashboard' %}" class="text-secondary">Home</a></li>
<li class="breadcrumb-item"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-secondary">Job:({{candidate.job.title}})</a></li> <li class="breadcrumb-item"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-secondary">Job:({{candidate.job.title}})</a></li>
<li class="breadcrumb-item active" aria-current="page" class="text-secondary">Applicant Detail</li> <li class="breadcrumb-item active" aria-current="page" class="text-secondary" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">Applicant Detail</li>
</ol> </ol>
</nav> </nav>

View File

@ -1,4 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
{% load i18n %}
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
@ -517,7 +518,10 @@
</style> </style>
</head> </head>
<body class="bg-kaauh-light-bg font-sans"> <body class="bg-kaauh-light-bg font-sans">
<div class="container container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;"> <div class="container container-fluid flex-grow-1" style="max-width: 1600px; margin: 0 auto;">
{% include 'recruitment/partials/ai_overview_breadcromb.html' %}
<!-- Header Section --> <!-- Header Section -->
<header class="header-box"> <header class="header-box">
<div class="header-info"> <div class="header-info">
@ -715,22 +719,10 @@
<span class="metric-value" style="color: var(--kaauh-teal);">{{ candidate.analysis_data.category }}</span> <span class="metric-value" style="color: var(--kaauh-teal);">{{ candidate.analysis_data.category }}</span>
</div> </div>
{% endif %} {% endif %}
<div style="margin-bottom: 1rem;">
<div class="analysis-metric" style="margin-bottom: 0.5rem; border-bottom: none;">
<span class="metric-title">Match Score</span>
<span class="metric-value">{{ candidate.analysis_data.match_score|default:0 }}/100</span>
</div>
<div class="progress-container">
<div class="progress-bar progress-bar-animated"
style="width: {{ candidate.analysis_data.match_score|default:0 }}%; background-color:
{% if candidate.analysis_data.match_score|default:0 < 50 %}var(--score-red){% elif candidate.analysis_data.match_score|default:0 < 75 %}var(--score-yellow){% else %}var(--score-green){% endif %}">
</div>
</div>
</div>
{% if candidate.analysis_data.red_flags %} {% if candidate.analysis_data.red_flags %}
<div class="narrative-box red-flag-box"> <div class="narrative-box">
<h3 class="flag-title red"><i class="fas fa-flag"></i>Red Flags</h3> <h3 class="flag-title red"><i class="fas fa-flag"></i>Red Flags</h3>
<!-- scoring_data.red_flags --> <!-- scoring_data.red_flags -->
<p class="narrative-text">{{ candidate.analysis_data.red_flags|join:". "|default:"None." }}</p> <p class="narrative-text">{{ candidate.analysis_data.red_flags|join:". "|default:"None." }}</p>

View File

@ -0,0 +1,118 @@
{% load i18n %}
{% with request.resolver_match as resolved %}
{% if 'resume-template' in resolved.route and resolved.kwargs.slug == candidate.slug %}
<nav aria-label="breadcrumb" style="font-family: 'Inter', sans-serif; margin-bottom:1.5rem;">
<ol style="
display: flex;
flex-wrap: wrap;
padding: 0;
margin: 0;
list-style: none;
font-size: 0.875rem; /* text-sm */
">
<li style="display: flex; align-items: center;">
<a
href="{% url 'dashboard' %}"
style="
color: #6c757d; /* text-secondary/gray */
text-decoration: none;
transition: color 0.15s ease-in-out;
"
onmouseover="this.style.color='#000000';"
onmouseout="this.style.color='#6c757d';"
>
{% translate 'Home' %}
</a>
</li>
<li style="
display: flex;
align-items: center;
padding-left: 0.5rem;
padding-right: 0.5rem;
">
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
</li>
<li style="display: flex; align-items: center;">
<a
href="{% url 'job_list' %}"
style="
color: #6c757d; /* text-secondary/gray */
text-decoration: none;
transition: color 0.15s ease-in-out;
"
onmouseover="this.style.color='#000000';"
onmouseout="this.style.color='#6c757d';"
>
{% trans 'Jobs' %}
</a>
</li>
<li style="
display: flex;
align-items: center;
padding-left: 0.5rem;
padding-right: 0.5rem;
">
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
</li>
<li style="display: flex; align-items: center;">
<a
href="{% url 'candidate_list' %}"
style="
color: #6c757d; /* text-secondary/gray */
text-decoration: none;
transition: color 0.15s ease-in-out;
"
onmouseover="this.style.color='#000000';"
onmouseout="this.style.color='#6c757d';"
>
{% trans 'Applicants' %}
</a>
</li>
<li style="
display: flex;
align-items: center;
padding-left: 0.5rem;
padding-right: 0.5rem;
">
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
</li>
<li style="display: flex; align-items: center;">
<a
href="{% url 'candidate_detail' candidate.slug %}"
style="
color: #6c757d; /* text-secondary/gray */
text-decoration: none;
transition: color 0.15s ease-in-out;
"
onmouseover="this.style.color='#000000';"
onmouseout="this.style.color='#6c757d';"
>
{{candidate.resume_data.full_name}}
</a>
</li>
<li style="
display: flex;
align-items: center;
padding-left: 0.5rem;
padding-right: 0.5rem;
">
<span style="color: #6c757d; opacity: 0.75;" aria-hidden="true">/</span>
</li>
<li
aria-current="page"
style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
"
>
{% trans 'AI Overview' %}
</li>
</ol>
</nav>
{% endif %}
{% endwith %}