diff --git a/recruitment/__pycache__/models.cpython-312.pyc b/recruitment/__pycache__/models.cpython-312.pyc
index 1dc5ea3..a8ac33d 100644
Binary files a/recruitment/__pycache__/models.cpython-312.pyc and b/recruitment/__pycache__/models.cpython-312.pyc differ
diff --git a/recruitment/__pycache__/views_frontend.cpython-312.pyc b/recruitment/__pycache__/views_frontend.cpython-312.pyc
index 85f9dab..72a41e0 100644
Binary files a/recruitment/__pycache__/views_frontend.cpython-312.pyc and b/recruitment/__pycache__/views_frontend.cpython-312.pyc differ
diff --git a/recruitment/models.py b/recruitment/models.py
index a0776f0..3f7a86e 100644
--- a/recruitment/models.py
+++ b/recruitment/models.py
@@ -338,6 +338,20 @@ class JobPosting(Base):
@property
def offer_candidates_count(self):
return self.all_candidates.filter(stage="Offer").count()
+
+ @property
+ def vacancy_fill_rate(self):
+ total_positions = self.open_positions
+
+ no_of_positions_filled = self.candidates.filter(stage__in=['HIRED']).count()
+
+ if total_positions > 0:
+ vacancy_fill_rate = no_of_positions_filled / total_positions
+ else:
+ vacancy_fill_rate = 0.0
+
+ return vacancy_fill_rate
+
class JobPostingImage(models.Model):
@@ -632,6 +646,7 @@ class Candidate(Base):
).exists()
return future_meetings or today_future_meetings
+
class TrainingMaterial(Base):
diff --git a/recruitment/views_frontend.py b/recruitment/views_frontend.py
index d204d97..2055fde 100644
--- a/recruitment/views_frontend.py
+++ b/recruitment/views_frontend.py
@@ -240,7 +240,7 @@ def candidate_detail(request, slug):
stage_form = None
if request.user.is_staff:
stage_form = forms.CandidateStageForm()
-
+
# parsed = JSON(json.dumps(parsed), indent=2, highlight=True, skip_keys=False, ensure_ascii=False, check_circular=True, allow_nan=True, default=None, sort_keys=False)
# parsed = json_to_markdown_table([parsed])
return render(request, 'recruitment/candidate_detail.html', {
@@ -395,12 +395,12 @@ def dashboard_view(request):
high_potential_ratio = round((high_potential_count / total_candidates) * 100, 1) if total_candidates > 0 else 0
jobs=models.JobPosting.objects.all().order_by('internal_job_id')
- selected_job_id=request.GET.get('selected_job_id','')
+ selected_job_pk=request.GET.get('selected_job_pk','')
candidate_stage=['APPLIED','EXAM','INTERVIEW','OFFER']
apply_count,exam_count,interview_count,offer_count=[0]*4
- if selected_job_id:
- job=jobs.get(internal_job_id=selected_job_id)
+ if selected_job_pk:
+ job=jobs.get(pk=selected_job_pk)
apply_count=job.screening_candidates_count
exam_count=job.exam_candidates_count
interview_count=job.interview_candidates_count
@@ -430,7 +430,7 @@ def dashboard_view(request):
'high_potential_count': high_potential_count,
'high_potential_ratio': high_potential_ratio,
'scored_ratio': scored_ratio,
- 'current_job_id':selected_job_id,
+ 'current_job_id':selected_job_pk,
'jobs':jobs,
'all_candidates_count':all_candidates_count,
'candidate_stage':json.dumps(candidate_stage),
diff --git a/templates/jobs/job_detail.html b/templates/jobs/job_detail.html
index 3d5139f..25b27f2 100644
--- a/templates/jobs/job_detail.html
+++ b/templates/jobs/job_detail.html
@@ -347,7 +347,7 @@
{% trans "Create New Form Template" %}
{% else %}
-
+
{% trans "View Form Template" %}
{% endif %}
@@ -474,6 +474,16 @@
+
+
+
+
+
+
{{ job.vacancy_fill_rate|floatformat:2 }}
+
{% trans "Vacancy Fill Rate" %}
+
+
+
diff --git a/templates/recruitment/candidate_detail.html b/templates/recruitment/candidate_detail.html
index f5a405f..5c33e40 100644
--- a/templates/recruitment/candidate_detail.html
+++ b/templates/recruitment/candidate_detail.html
@@ -176,6 +176,59 @@
.timeline-bg-interview { background-color: #ffc107 !important; }
.timeline-bg-offer { background-color: #28a745 !important; }
.timeline-bg-rejected { background-color: #dc3545 !important; }
+
+
+
+
+/* ------------------------------------------- */
+/* 1. Base Spinner Styling */
+/* ------------------------------------------- */
+.kaats-spinner {
+ animation: kaats-spinner-rotate 1.5s linear infinite; /* Faster rotation */
+ width: 40px; /* Standard size */
+ height: 40px;
+ display: inline-block; /* Useful for table cells */
+ vertical-align: middle;
+}
+
+.kaats-spinner .path {
+ stroke: var(--kaauh-teal, #00636e); /* Use Teal color, fallback to dark teal */
+ stroke-linecap: round;
+ /* Optional: Add a lighter background circle for contrast */
+ /* stroke-dashoffset will be reset by the dash animation */
+}
+
+/* Optional: Background circle for better contrast (similar to Bootstrap) */
+.kaats-spinner circle {
+ stroke: var(--kaauh-border, #e9ecef); /* Light gray background */
+ fill: none;
+ stroke-width: 5; /* Keep stroke-width on both circles */
+}
+
+/* ------------------------------------------- */
+/* 2. Keyframe Animations */
+/* ------------------------------------------- */
+@keyframes kaats-spinner-rotate {
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes kaats-spinner-dash {
+ 0% {
+ stroke-dasharray: 1, 150;
+ stroke-dashoffset: 0;
+ }
+ 50% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -35;
+ }
+ 100% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -124;
+ }
+}
+
{% endblock %}
@@ -572,8 +625,20 @@
-
-{% include 'recruitment/candidate_resume_template.html' %}
+{% if candidate.is_resume_parsed %}
+ {% include 'recruitment/candidate_resume_template.html' %}
+{% else %}
+
+
+
+ AI Score Loading.....
+
+
+{% endif %}
{% if user.is_staff %}
diff --git a/templates/recruitment/candidate_list.html b/templates/recruitment/candidate_list.html
index 72867db..41c2be9 100644
--- a/templates/recruitment/candidate_list.html
+++ b/templates/recruitment/candidate_list.html
@@ -132,6 +132,57 @@
display: flex;
gap: 0.5rem;
}
+
+
+/* ------------------------------------------- */
+/* 1. Base Spinner Styling */
+/* ------------------------------------------- */
+.kaats-spinner {
+ animation: kaats-spinner-rotate 1.5s linear infinite; /* Faster rotation */
+ width: 40px; /* Standard size */
+ height: 40px;
+ display: inline-block; /* Useful for table cells */
+ vertical-align: middle;
+}
+
+.kaats-spinner .path {
+ stroke: var(--kaauh-teal, #00636e); /* Use Teal color, fallback to dark teal */
+ stroke-linecap: round;
+ /* Optional: Add a lighter background circle for contrast */
+ /* stroke-dashoffset will be reset by the dash animation */
+}
+
+/* Optional: Background circle for better contrast (similar to Bootstrap) */
+.kaats-spinner circle {
+ stroke: var(--kaauh-border, #e9ecef); /* Light gray background */
+ fill: none;
+ stroke-width: 5; /* Keep stroke-width on both circles */
+}
+
+/* ------------------------------------------- */
+/* 2. Keyframe Animations */
+/* ------------------------------------------- */
+@keyframes kaats-spinner-rotate {
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes kaats-spinner-dash {
+ 0% {
+ stroke-dasharray: 1, 150;
+ stroke-dashoffset: 0;
+ }
+ 50% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -35;
+ }
+ 100% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -124;
+ }
+}
+
{% endblock %}
@@ -232,11 +283,23 @@
{{ candidate.phone }} |
{{ candidate.job.title }} |
+ {% if candidate.is_resume_parsed %}
{% if candidate.professional_category != 'Uncategorized' %}
{{ candidate.professional_category }}
- {% endif %}
+ {% endif %}
+ {% else %}
+
+
+
+
+
+ {% endif %}
|
diff --git a/templates/recruitment/candidate_screening_view.html b/templates/recruitment/candidate_screening_view.html
index b7e33b8..b828b8c 100644
--- a/templates/recruitment/candidate_screening_view.html
+++ b/templates/recruitment/candidate_screening_view.html
@@ -163,7 +163,51 @@
}
+.kaats-spinner {
+ animation: kaats-spinner-rotate 1.5s linear infinite; /* Faster rotation */
+ width: 40px; /* Standard size */
+ height: 40px;
+ display: inline-block; /* Useful for table cells */
+ vertical-align: middle;
+}
+.kaats-spinner .path {
+ stroke: var(--kaauh-teal, #00636e); /* Use Teal color, fallback to dark teal */
+ stroke-linecap: round;
+ /* Optional: Add a lighter background circle for contrast */
+ /* stroke-dashoffset will be reset by the dash animation */
+}
+
+/* Optional: Background circle for better contrast (similar to Bootstrap) */
+.kaats-spinner circle {
+ stroke: var(--kaauh-border, #e9ecef); /* Light gray background */
+ fill: none;
+ stroke-width: 5; /* Keep stroke-width on both circles */
+}
+
+/* ------------------------------------------- */
+/* 2. Keyframe Animations */
+/* ------------------------------------------- */
+@keyframes kaats-spinner-rotate {
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes kaats-spinner-dash {
+ 0% {
+ stroke-dasharray: 1, 150;
+ stroke-dashoffset: 0;
+ }
+ 50% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -35;
+ }
+ 100% {
+ stroke-dasharray: 90, 150;
+ stroke-dashoffset: -124;
+ }
+}
{% endblock %}
{% block content %}
@@ -351,11 +395,21 @@
|
+ {% if candidate.is_resume_parsed %}
{% if candidate.match_score %}
{{ candidate.match_score|default:"0" }}%
{% endif %}
+ {% else %}
+
+
+
+ {% endif %}
|
{% if candidate.screening_stage_rating %}
diff --git a/templates/recruitment/dashboard.html b/templates/recruitment/dashboard.html
index d55c31b..c297668 100644
--- a/templates/recruitment/dashboard.html
+++ b/templates/recruitment/dashboard.html
@@ -123,7 +123,7 @@
{% block content %}
- {% trans "Recruitment Intelligence" %} ðŸ§
+ {% trans "Recruitment Analytics" %}
{# -------------------------------------------------------------------------- #}
{# STATS CARDS SECTION #}
@@ -156,7 +156,7 @@
{{ avg_match_score|floatformat:1 }}
{% trans "Average AI Score (0-100)" %}
@@ -164,7 +164,7 @@
{{ high_potential_count }}
{% trans "Candidates with Score ≥ 75 ({{ high_potential_ratio }}%)" %}
@@ -172,7 +172,7 @@
{{ scored_ratio|floatformat:1 }}%
{% trans "Percent of profiles processed by AI" %}
@@ -210,13 +210,13 @@
{{my_job}}
{# Job Filter Dropdown - Consistent with Card Header Layout #}
- |