Compare commits
6 Commits
6447a895c0
...
9eb2b90da6
| Author | SHA1 | Date | |
|---|---|---|---|
| 9eb2b90da6 | |||
| c959c8a944 | |||
| 465936238e | |||
| c73f372a46 | |||
| 1368771896 | |||
| a181e84569 |
Binary file not shown.
Binary file not shown.
@ -321,23 +321,23 @@ class JobPosting(Base):
|
||||
def all_candidates_count(self):
|
||||
return self.candidates.annotate(
|
||||
sortable_score=Cast('ai_analysis_data__match_score', output_field=CharField())).order_by(
|
||||
'-sortable_score').count()
|
||||
'-sortable_score').count() or 0
|
||||
|
||||
@property
|
||||
def screening_candidates_count(self):
|
||||
return self.all_candidates.filter(stage="Applied").count()
|
||||
return self.all_candidates.filter(stage="Applied").count() or 0
|
||||
|
||||
@property
|
||||
def exam_candidates_count(self):
|
||||
return self.all_candidates.filter(stage="Exam").count()
|
||||
return self.all_candidates.filter(stage="Exam").count() or 0
|
||||
|
||||
@property
|
||||
def interview_candidates_count(self):
|
||||
return self.all_candidates.filter(stage="Interview").count()
|
||||
return self.all_candidates.filter(stage="Interview").count() or 0
|
||||
|
||||
@property
|
||||
def offer_candidates_count(self):
|
||||
return self.all_candidates.filter(stage="Offer").count()
|
||||
return self.all_candidates.filter(stage="Offer").count() or 0
|
||||
|
||||
@property
|
||||
def vacancy_fill_rate(self):
|
||||
@ -647,6 +647,11 @@ class Candidate(Base):
|
||||
|
||||
return future_meetings or today_future_meetings
|
||||
|
||||
# @property
|
||||
# def time_to_hire(self):
|
||||
# time_to_hire=self.hired_date-self.created_at
|
||||
# return time_to_hire
|
||||
|
||||
|
||||
|
||||
class TrainingMaterial(Base):
|
||||
|
||||
@ -339,6 +339,8 @@ class TrainingDeleteView(LoginRequiredMixin, SuccessMessageMixin, DeleteView):
|
||||
|
||||
@login_required
|
||||
def dashboard_view(request):
|
||||
all_candidates_count=0
|
||||
|
||||
# --- Performance Optimization: Aggregate Data in ONE Query ---
|
||||
|
||||
# 1. Base Job Query: Get all jobs and annotate with candidate count
|
||||
@ -393,27 +395,33 @@ def dashboard_view(request):
|
||||
sortable_score__gte=75
|
||||
).count()
|
||||
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_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_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
|
||||
offer_count=job.offer_candidates_count
|
||||
all_candidates_count=job.all_candidates_count
|
||||
try:
|
||||
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
|
||||
offer_count=job.offer_candidates_count
|
||||
all_candidates_count=job.all_candidates_count
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
else: #default job
|
||||
job=jobs.first()
|
||||
apply_count=job.screening_candidates_count
|
||||
exam_count=job.exam_candidates_count
|
||||
interview_count=job.interview_candidates_count
|
||||
offer_count=job.offer_candidates_count
|
||||
all_candidates_count=job.all_candidates_count
|
||||
try:
|
||||
job=jobs.first()
|
||||
apply_count=job.screening_candidates_count
|
||||
exam_count=job.exam_candidates_count
|
||||
interview_count=job.interview_candidates_count
|
||||
offer_count=job.offer_candidates_count
|
||||
all_candidates_count=job.all_candidates_count
|
||||
except Exception as e:
|
||||
print(e)
|
||||
candidates_count=[ apply_count,exam_count,interview_count,offer_count ]
|
||||
|
||||
context = {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
</p>
|
||||
|
||||
<p style="font-family: Arial, sans-serif; font-size: 16px; color: #333;">
|
||||
{% blocktrans %}Thank you for choosing **KAAUH ATS**. To verify the ownership of your email address, please click the confirmation link below:{% endblocktrans %}
|
||||
{% blocktrans %}To verify the ownership of your email address, please click the confirmation link below:{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<div style="text-align: center; margin: 30px 0;">
|
||||
|
||||
18
templates/account/email/email_confirmation_message.txt
Normal file
18
templates/account/email/email_confirmation_message.txt
Normal file
@ -0,0 +1,18 @@
|
||||
{% load account i18n %}
|
||||
{% autoescape off %}
|
||||
|
||||
{% blocktrans %}Hello,{% endblocktrans %}
|
||||
|
||||
{% blocktrans %}To verify the ownership of your email address, please click the confirmation link below:{% endblocktrans %}
|
||||
|
||||
|
||||
{% trans "Confirm My KAAUH ATS Email" %}
|
||||
{{ activate_url }}
|
||||
|
||||
|
||||
{% blocktrans %}If you did not request this verification, you can safely ignore this email.{% endblocktrans %}
|
||||
|
||||
{% blocktrans %}Alternatively, copy and paste this link into your browser:{% endblocktrans %}
|
||||
{{ activate_url }}
|
||||
|
||||
{% endautoescape %}
|
||||
27
templates/account/email/password_reset_key_message.txt
Normal file
27
templates/account/email/password_reset_key_message.txt
Normal file
@ -0,0 +1,27 @@
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% autoescape off %}
|
||||
|
||||
{% trans "Password Reset Request" %}
|
||||
|
||||
{% trans "Hello," %}
|
||||
|
||||
{% blocktrans %}You are receiving this email because you or someone else has requested a password reset for your account at{% endblocktrans %} {{ current_site.name }}.
|
||||
|
||||
------------------------------------------------------
|
||||
{% trans "Click Here to Reset Your Password" %}
|
||||
{{ password_reset_url }}
|
||||
------------------------------------------------------
|
||||
|
||||
{% trans "This link is only valid for a limited time." %}
|
||||
|
||||
{% trans "If you did not request a password reset, please ignore this email. Your password will remain unchanged." %}
|
||||
|
||||
{% trans "Thank you," %}
|
||||
{% trans "KAAUH ATS Team" %}
|
||||
|
||||
---
|
||||
{% trans "If the button above does not work, copy and paste the following link into your browser:" %}
|
||||
{{ password_reset_url }}
|
||||
|
||||
{% endautoescape %}
|
||||
@ -1,6 +1,8 @@
|
||||
{% load static %}
|
||||
{% load static i18n %}
|
||||
{% get_current_language_bidi as LANGUAGE_BIDI %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="{{LANGUAGE_CODE}}" dir="{% if LANGUAGE_BIDI %}rtl{% else %}ltr{% endif %}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
@ -146,34 +148,34 @@
|
||||
|
||||
<div class="right-panel-content-wrapper">
|
||||
|
||||
<h2 id="form-title" class="h3 fw-bold mb-4 text-center">Sign In</h2>
|
||||
<h2 id="form-title" class="h3 fw-bold mb-4 text-center">{% trans "Sign In" %}</h2>
|
||||
|
||||
<div class="form-fields">
|
||||
<form id="login-form" class="space-y-4" method="post" action="{% url 'account_login' %}">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_login" class="form-label fw-semibold">Username or Email *</label>
|
||||
<input type="text" name="login" id="id_login" class="form-control" placeholder="Enter username or email" required autofocus>
|
||||
<label for="id_login" class="form-label fw-semibold"> {% trans "Email *" %}</label>
|
||||
<input type="text" name="login" id="id_login" class="form-control" placeholder="{% trans 'Enter your email' %}" required autofocus>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_password" class="form-label fw-semibold">Password *</label>
|
||||
<input type="password" name="password" id="id_password" class="form-control" placeholder="Password" required>
|
||||
<label for="id_password" class="form-label fw-semibold">{% trans "Password *" %}</label>
|
||||
<input type="password" name="password" id="id_password" class="form-control" placeholder="{% trans 'Password' %}" required>
|
||||
|
||||
<div class="text-end mt-2">
|
||||
<a href="{% url 'account_reset_password' %}" class="small text-accent fw-medium">Forgot Password?</a>
|
||||
<a href="{% url 'account_reset_password' %}" class="small text-accent fw-medium">{% trans 'Forgot Password?' %}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check pt-3">
|
||||
<input class="form-check-input" type="checkbox" name="remember" id="id_remember">
|
||||
<label class="form-check-label text-muted" for="id_remember">
|
||||
Keep me signed in
|
||||
{% trans "Keep me signed in" %}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary w-100 mt-4">Sign In</button>
|
||||
<button type="submit" class="btn btn-primary w-100 mt-4">{% trans "Sign In" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -11,9 +11,11 @@
|
||||
/* ================================================= */
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-light: #007a88;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-border: #eaeff3;
|
||||
--kaauh-primary-text: #343a40;
|
||||
--kaauh-accent: #ffcc00;
|
||||
}
|
||||
|
||||
/* Primary Color Overrides */
|
||||
@ -179,30 +181,43 @@
|
||||
|
||||
|
||||
|
||||
|
||||
/* ------------------------------------------- */
|
||||
/* 1. Base Spinner Styling */
|
||||
/* ------------------------------------------- */
|
||||
.ai-loading-container {
|
||||
/* Flex container to center content and align icon/spinner */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px; /* Space between robot icon and spinner */
|
||||
font-size: 14px; /* Default text size */
|
||||
color: var(--kaauh-teal-light); /* Loading text color */
|
||||
}
|
||||
|
||||
.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 */
|
||||
animation: kaats-spinner-rotate 2s linear infinite; /* Slower, more deliberate rotation */
|
||||
width: 100px; /* Smaller size to be subtle */
|
||||
height: 100px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.kaats-spinner .path {
|
||||
stroke: var(--kaauh-teal, #00636e); /* Use Teal color, fallback to dark teal */
|
||||
stroke: var(--kaauh-teal-light, #007a88); /* Lighter Teal for the active path */
|
||||
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 */
|
||||
stroke-width: 4; /* Slightly smaller stroke-width */
|
||||
}
|
||||
|
||||
/* New: Robot Icon Style and Animation */
|
||||
.ai-robot-icon {
|
||||
font-size: 2rem; /* Slightly larger than text */
|
||||
color: var(--kaauh-teal);
|
||||
animation: ai-pulse 2s infinite ease-in-out; /* Pulsing effect for 'intelligence' */
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
/* ------------------------------------------- */
|
||||
@ -229,6 +244,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* New Keyframe: AI Pulse effect */
|
||||
@keyframes ai-pulse {
|
||||
0% {
|
||||
color: var(--kaauh-teal);
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
color: var(--kaauh-accent); /* Accent color pulse */
|
||||
transform: scale(1.05);
|
||||
}
|
||||
100% {
|
||||
color: var(--kaauh-teal);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@ -587,7 +618,7 @@
|
||||
|
||||
{# ACTIONS CARD #}
|
||||
|
||||
<div class="card shadow-sm mb-4 p-3">
|
||||
<div class="card shadow-sm mb-2 p-3">
|
||||
<h5 class="text-muted mb-3"><i class="fas fa-cog me-2"></i>{% trans "Management Actions" %}</h5>
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-primary">
|
||||
@ -618,6 +649,14 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-4 p-1">
|
||||
<h5 class="text-muted mb-3"><i class="fas fa-clock me-2"></i>{% trans "Time to Hire" %}</h5>
|
||||
<div class="d-grid gap-2 text-center">
|
||||
{{candidate.time_to_hire|default:"50 days"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
@ -629,14 +668,23 @@
|
||||
{% include 'recruitment/candidate_resume_template.html' %}
|
||||
{% else %}
|
||||
<a href="{% url 'candidate_detail' candidate.slug %}" class="text-decoration-none">
|
||||
<div style="display: flex; justify-content: center; align-items: center; height: 100%;">
|
||||
<svg class="kaats-spinner" viewBox="0 0 50 50" style="width: 100px; height: 100px;">
|
||||
<circle cx="25" cy="25" r="20"></circle>
|
||||
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"
|
||||
style="animation: kaats-spinner-dash 1.5s ease-in-out infinite;"></circle>
|
||||
</svg>
|
||||
<span class="ms-2">AI Score Loading.....</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: center; align-items: center; height: 100%;">
|
||||
|
||||
<div class="ai-loading-container">
|
||||
{# Robot Icon (Requires Font Awesome or similar library) #}
|
||||
<i class="fas fa-robot ai-robot-icon"></i>
|
||||
|
||||
{# The Spinner #}
|
||||
<svg class="kaats-spinner" viewBox="0 0 50 50">
|
||||
<circle cx="25" cy="25" r="20"></circle>
|
||||
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="4"
|
||||
style="animation: kaats-spinner-dash 1.5s ease-in-out infinite;"></circle>
|
||||
</svg>
|
||||
|
||||
<span>AI Scoring...</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
|
||||
@ -254,6 +254,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if candidates %}
|
||||
<div id="candidate-list">
|
||||
{# View Switcher - list_id must match the container ID #}
|
||||
@ -290,13 +291,14 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<a href="{% url 'candidate_list' %}">
|
||||
<a href="{% url 'candidate_list' %}" class="text-decoration-none">
|
||||
<div>
|
||||
<svg class="kaats-spinner" viewBox="0 0 50 50" style="width: 25px; height: 25px;">
|
||||
<circle cx="25" cy="25" r="20"></circle>
|
||||
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"
|
||||
style="animation: kaats-spinner-dash 1.5s ease-in-out infinite;"></circle>
|
||||
</svg>
|
||||
<span class="text-teal-primary">{% trans "AI Scoring..." %}</span>
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@ -395,19 +395,20 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if candidate.is_resume_parsed %}
|
||||
{% if candidate.is_resume_parsed %}
|
||||
{% if candidate.match_score %}
|
||||
<span class="badge ai-score-badge">
|
||||
{{ candidate.match_score|default:"0" }}%
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div>
|
||||
{% else %}
|
||||
<div class="text-nowrap">
|
||||
<svg class="kaats-spinner" viewBox="0 0 50 50" style="width: 25px; height: 25px;">
|
||||
<circle cx="25" cy="25" r="20"></circle>
|
||||
<circle class="path" cx="25" cy="25" r="20" fill="none" stroke-width="5"
|
||||
style="animation: kaats-spinner-dash 1.5s ease-in-out infinite;"></circle>
|
||||
</svg>
|
||||
<span class="text-teal-primary">{% trans 'AI scoring..' %}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
@ -167,7 +167,7 @@
|
||||
<h3><i class="fas fa-trophy stat-icon"></i> {% trans "High Potential" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ high_potential_count }}</div>
|
||||
<div class="stat-caption">{% trans "Candidates with Score ≥ 75 ({{ high_potential_ratio }}%)" %}</div>
|
||||
<div class="stat-caption">{% trans "Candidates with Score ≥ 75%" %} ({{ high_potential_ratio }})</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
|
||||
@ -52,7 +52,7 @@
|
||||
<i class="fas fa-times me-1"></i>Cancel
|
||||
</button>
|
||||
|
||||
<button type="submit" class="btn btn-primary" id="stageUpdateSubmit">
|
||||
<button type="submit" class="btn btn-main-action" id="stageUpdateSubmit">
|
||||
<i class="fas fa-save me-1"></i>
|
||||
<span class="ms-2">Update</span>
|
||||
</button>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user