Merge pull request 'frontend' (#28) from frontend into main
Reviewed-on: #28
This commit is contained in:
commit
15f8cb2650
4
.gitignore
vendored
4
.gitignore
vendored
@ -27,7 +27,7 @@ var/
|
||||
*.log
|
||||
*.pot
|
||||
*.sqlite3
|
||||
local_settings.py
|
||||
settings.py
|
||||
db.sqlite3
|
||||
|
||||
# Virtual environment
|
||||
@ -95,7 +95,7 @@ coverage.xml
|
||||
# Django stuff:
|
||||
|
||||
# Local settings
|
||||
local_settings.py
|
||||
settings.py
|
||||
|
||||
# Database sqlite files:
|
||||
# The base directory for relative paths in .gitignore
|
||||
|
||||
Binary file not shown.
@ -66,7 +66,7 @@ INSTALLED_APPS = [
|
||||
SITE_ID = 1
|
||||
|
||||
|
||||
LOGIN_REDIRECT_URL = '/'
|
||||
LOGIN_REDIRECT_URL = 'dashboard'
|
||||
|
||||
|
||||
ACCOUNT_LOGOUT_REDIRECT_URL = '/'
|
||||
@ -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',
|
||||
}
|
||||
@ -183,32 +183,19 @@ ACCOUNT_LOGIN_METHODS = ['email']
|
||||
ACCOUNT_SIGNUP_FIELDS = ['email*', 'password1*', 'password2*']
|
||||
|
||||
ACCOUNT_UNIQUE_EMAIL = True
|
||||
ACCOUNT_EMAIL_VERIFICATION = 'none'
|
||||
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
|
||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||
ACCOUNT_LOGIN_ON_EMAIL_CONFIRMATION = True
|
||||
|
||||
|
||||
ACCOUNT_FORMS = {'signup': 'recruitment.forms.StaffSignupForm'}
|
||||
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
EMAIL_HOST = '10.10.1.110' #'smtp.gmail.com'
|
||||
EMAIL_PORT = 2225 #587
|
||||
EMAIL_USE_TLS = False
|
||||
EMAIL_USE_SSL = False
|
||||
EMAIL_TIMEOUT = 10
|
||||
|
||||
DEFAULT_FROM_EMAIL = 'norahuniversity@example.com'
|
||||
|
||||
# Gmail SMTP credentials
|
||||
# Remove the comment below if you want to use Gmail SMTP server
|
||||
# EMAIL_HOST_USER = 'your_email@gmail.com'
|
||||
# EMAIL_HOST_PASSWORD = 'your_password'
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
|
||||
# Crispy Forms Configuration
|
||||
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
|
||||
CRISPY_TEMPLATE_PACK = "bootstrap5"
|
||||
CRISPY_TEMPLATE_PACK = "bootstrapconsole5"
|
||||
|
||||
# Bootstrap 5 Configuration
|
||||
CRISPY_BS5 = {
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -162,10 +162,17 @@ def send_interview_invitation_email(candidate, job, meeting_details=None, recipi
|
||||
try:
|
||||
# Prepare recipient list
|
||||
recipients = []
|
||||
if candidate.email:
|
||||
if candidate.hiring_source == "Agency":
|
||||
try:
|
||||
recipients.append(candidate.hiring_agency.email)
|
||||
except :
|
||||
pass
|
||||
else:
|
||||
recipients.append(candidate.email)
|
||||
|
||||
if recipient_list:
|
||||
recipients.extend(recipient_list)
|
||||
|
||||
|
||||
if not recipients:
|
||||
return {'success': False, 'error': 'No recipient email addresses provided'}
|
||||
|
||||
@ -682,7 +682,30 @@ class Candidate(Base):
|
||||
@property
|
||||
def scoring_timeout(self):
|
||||
return timezone.now() <= (self.created_at + timezone.timedelta(minutes=5))
|
||||
|
||||
|
||||
@property
|
||||
def get_interview_date(self):
|
||||
if hasattr(self, 'scheduled_interview') and self.scheduled_interview:
|
||||
return self.scheduled_interviews.first().interview_date
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def get_interview_time(self):
|
||||
if hasattr(self, 'scheduled_interview') and self.scheduled_interview:
|
||||
return self.scheduled_interviews.first().interview_time
|
||||
return None
|
||||
|
||||
|
||||
@property
|
||||
def time_to_hire_days(self):
|
||||
if self.hired_date and self.created_at:
|
||||
time_to_hire = self.hired_date - self.created_at.date()
|
||||
return time_to_hire.days
|
||||
return 0
|
||||
|
||||
class TrainingMaterial(Base):
|
||||
title = models.CharField(max_length=255, verbose_name=_("Title"))
|
||||
|
||||
@ -5,7 +5,7 @@ from . import views_integration
|
||||
from . import views_source
|
||||
|
||||
urlpatterns = [
|
||||
path('', views_frontend.dashboard_view, name='dashboard'),
|
||||
path('dashboard/', views_frontend.dashboard_view, name='dashboard'),
|
||||
|
||||
# Job URLs (using JobPosting model)
|
||||
path('jobs/', views_frontend.JobListView.as_view(), name='job_list'),
|
||||
@ -62,6 +62,7 @@ urlpatterns = [
|
||||
|
||||
# Form Preview URLs
|
||||
# path('forms/', views.form_list, name='form_list'),
|
||||
|
||||
path('forms/builder/', views.form_builder, name='form_builder'),
|
||||
path('forms/builder/<slug:template_slug>/', views.form_builder, name='form_builder'),
|
||||
path('forms/', views.form_templates_list, name='form_templates_list'),
|
||||
|
||||
@ -449,7 +449,7 @@ def schedule_interviews(schedule):
|
||||
interview_date=slot['date'],
|
||||
interview_time=slot['time']
|
||||
)
|
||||
|
||||
candidate.interview_date=interview_datetime
|
||||
# Send email to candidate
|
||||
send_interview_email(scheduled_interview)
|
||||
|
||||
|
||||
@ -860,13 +860,13 @@ def application_submit_form(request, template_slug):
|
||||
if is_limit_exceeded:
|
||||
messages.error(
|
||||
request,
|
||||
'Application limit reached: This job is no longer accepting new applications. Please explore other available positions.'
|
||||
_('Application limit reached: This job is no longer accepting new applications.')
|
||||
)
|
||||
return redirect('application_detail',slug=job.slug)
|
||||
if job.is_expired:
|
||||
messages.error(
|
||||
request,
|
||||
'Application deadline passed: This job is no longer accepting new applications. Please explore other available positions.'
|
||||
_('Application deadline passed: This job is no longer accepting new applications.')
|
||||
)
|
||||
return redirect('application_detail',slug=job.slug)
|
||||
|
||||
@ -1424,10 +1424,26 @@ def candidate_set_exam_date(request, slug):
|
||||
def candidate_update_status(request, slug):
|
||||
job = get_object_or_404(JobPosting, slug=slug)
|
||||
mark_as = request.POST.get('mark_as')
|
||||
|
||||
if mark_as != '----------':
|
||||
candidate_ids = request.POST.getlist("candidate_ids")
|
||||
print(candidate_ids)
|
||||
if c := Candidate.objects.filter(pk__in = candidate_ids):
|
||||
c.update(stage=mark_as,exam_date=timezone.now(),applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
|
||||
|
||||
if mark_as=='Exam':
|
||||
c.update(exam_date=timezone.now(),interview_date=None,offer_date=None,hired_date=None,stage=mark_as,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
|
||||
elif mark_as=='Interview':
|
||||
# interview_date update when scheduling the interview
|
||||
c.update(stage=mark_as,offer_date=None,hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
|
||||
elif mark_as=='Offer':
|
||||
c.update(stage=mark_as,offer_date=timezone.now(),hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
|
||||
elif mark_as=='Hired':
|
||||
print('hired')
|
||||
c.update(stage=mark_as,hired_date=timezone.now(),applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
|
||||
else:
|
||||
c.update(stage=mark_as,exam_date=None,interview_date=None,offer_date=None,hired_date=None,applicant_status="Candidate" if mark_as in ["Exam","Interview","Offer"] else "Applicant")
|
||||
|
||||
|
||||
|
||||
messages.success(request, f"Candidates Updated")
|
||||
response = HttpResponse(redirect("candidate_screening_view", slug=job.slug))
|
||||
@ -2973,11 +2989,12 @@ def agency_assignment_create(request,slug=None):
|
||||
messages.success(request, f'Assignment created for {assignment.agency.name} - {assignment.job.title}!')
|
||||
return redirect('agency_assignment_detail', slug=assignment.slug)
|
||||
else:
|
||||
messages.error(request, 'Please correct the errors below.')
|
||||
messages.error(request, f'Please correct the errors below.{form.errors.as_text()}')
|
||||
print(form.errors.as_json())
|
||||
else:
|
||||
form = AgencyJobAssignmentForm()
|
||||
try:
|
||||
from django.forms import HiddenInput
|
||||
# from django.forms import HiddenInput
|
||||
form.initial['agency'] = agency
|
||||
# form.fields['agency'].widget = HiddenInput()
|
||||
except HiringAgency.DoesNotExist:
|
||||
@ -3086,6 +3103,7 @@ def agency_access_link_detail(request, slug):
|
||||
AgencyAccessLink.objects.select_related('assignment__agency', 'assignment__job'),
|
||||
slug=slug
|
||||
)
|
||||
|
||||
|
||||
context = {
|
||||
'access_link': access_link,
|
||||
|
||||
@ -257,6 +257,7 @@ def candidate_detail(request, slug):
|
||||
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', {
|
||||
@ -458,19 +459,25 @@ def dashboard_view(request):
|
||||
|
||||
# B. Efficiency & Conversion Metrics (Scoped)
|
||||
hired_candidates = candidate_queryset.filter(
|
||||
Q(offer_status="Accepted") | Q(stage='HIRED'),
|
||||
join_date__isnull=False
|
||||
stage='Hired'
|
||||
)
|
||||
print(hired_candidates)
|
||||
lst=[c.time_to_hire_days for c in hired_candidates]
|
||||
print(lst)
|
||||
time_to_hire_query = hired_candidates.annotate(
|
||||
time_diff=ExpressionWrapper(
|
||||
F('join_date') - F('created_at__date'),
|
||||
F('hired_date') - F('created_at__date'),
|
||||
output_field=fields.DurationField()
|
||||
)
|
||||
).aggregate(avg_time_to_hire=Avg('time_diff'))
|
||||
|
||||
print(time_to_hire_query)
|
||||
|
||||
avg_time_to_hire_days = (
|
||||
time_to_hire_query.get('avg_time_to_hire').days
|
||||
if time_to_hire_query.get('avg_time_to_hire') else 0
|
||||
)
|
||||
print(avg_time_to_hire_days)
|
||||
|
||||
applied_count = candidate_queryset.filter(stage='Applied').count()
|
||||
advanced_count = candidate_queryset.filter(stage__in=['Exam', 'Interview', 'Offer']).count()
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% translate "Application Submitted - Thank You" %}</title>
|
||||
<title>{% trans "Application Submitted - Thank You" %}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<style>
|
||||
@ -166,25 +166,25 @@
|
||||
</div>
|
||||
|
||||
<h1 class="text-success-header">{% translate "Thank You!" %}</h1>
|
||||
<h2 class="h4" style="color: #333;">{% translate "Your application has been submitted successfully" %}</h2>
|
||||
<h2 class="h4" style="color: #333;">{% trans "Your application has been submitted successfully" %}</h2>
|
||||
|
||||
{% comment %} {# JOB INFO BLOCK #}
|
||||
<div class="job-info-block">
|
||||
<p class="mb-2"><strong>{% translate "Position" %}:</strong> <span class="fw-bold">{{ job.title }}</span></p>
|
||||
<p class="mb-2"><strong>{% translate "Job ID" %}:</strong> {{ job.internal_job_id }}</p>
|
||||
<p class="mb-2"><strong>{% translate "Department" %}:</strong> {{ job.department|default:"Not specified" }}</p>
|
||||
<p class="mb-2"><strong>{% trans "Position" %}:</strong> <span class="fw-bold">{{ job.title }}</span></p>
|
||||
<p class="mb-2"><strong>{% trans "Job ID" %}:</strong> {{ job.internal_job_id }}</p>
|
||||
<p class="mb-2"><strong>{% trans "Department" %}:</strong> {{ job.department|default:"Not specified" }}</p>
|
||||
{% if job.application_deadline %}
|
||||
<p><strong>{% translate "Application Deadline" %}:</strong> {{ job.application_deadline|date:"F j, Y" }}</p>
|
||||
<p><strong>{% trans "Application Deadline" %}:</strong> {{ job.application_deadline|date:"F j, Y" }}</p>
|
||||
{% endif %}
|
||||
</div> {% endcomment %}
|
||||
|
||||
<p style="font-size: 1rem; line-height: 1.6; color: #555;">
|
||||
{% translate "We appreciate your interest in joining our team. Our hiring team will review your application and contact you if there's a potential match for this position." %}
|
||||
{% trans "We appreciate your interest in joining our team. Our hiring team will review your application and contact you if there's a potential match for this position." %}
|
||||
</p>
|
||||
|
||||
<div style="margin-top: 30px;">
|
||||
<a href="https://kaauh.edu.sa/career" class="btn btn-main-action btn-lg">
|
||||
<i class="fas fa-arrow-left me-2"></i> {% translate "Return to Job Listings" %}
|
||||
<a href="{% url 'kaauh_career' %}" class="btn btn-main-action btn-lg">
|
||||
<i class="fas fa-arrow-left me-2"></i> {% trans "Return to Job Listings" %}
|
||||
</a>
|
||||
{# You can add a link to view the saved application here if applicable #}
|
||||
{% comment %} <a href="{% url 'jobs:job_detail' job.internal_job_id %}" class="btn btn-secondary">View Job Details</a> {% endcomment %}
|
||||
|
||||
@ -251,25 +251,25 @@
|
||||
</div>
|
||||
|
||||
{# Description Blocks (Main Content) #}
|
||||
{% if job.description %}
|
||||
{% if job.has_description_content %}
|
||||
<div class="mb-4">
|
||||
<h5>{% trans "Job Description" %}</h5>
|
||||
<div class="text-secondary">{{ job.description|safe }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if job.qualifications %}
|
||||
{% if job.has_qualifications_content %}
|
||||
<div class="mb-4">
|
||||
<h5>{% trans "Required Qualifications" %}</h5>
|
||||
<div class="text-secondary">{{ job.qualifications|safe }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if job.benefits %}
|
||||
{% if job.has_benefits_content %}
|
||||
<div class="mb-4">
|
||||
<h5>{% trans "Benefits" %}</h5>
|
||||
<div class="text-secondary">{{ job.benefits|safe}}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if job.application_instructions %}
|
||||
{% if job.has_application_instructions_content %}
|
||||
<div class="mb-4">
|
||||
<h5>{% trans "Application Instructions" %}</h5>
|
||||
<div class="text-secondary">{{ job.application_instructions|safe }}</div>
|
||||
@ -347,11 +347,14 @@
|
||||
<i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %}
|
||||
</a>
|
||||
{% else %}
|
||||
{% if job.form_template.is_active %}
|
||||
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-outline-secondary w-100">
|
||||
<i class="fas fa-list-alt me-1"></i> {% trans "View Form Template" %}
|
||||
</a>
|
||||
{% else %}
|
||||
{% if job.form_template.is_active %}
|
||||
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-outline-secondary w-100">
|
||||
<i class="fas fa-list-alt me-1"></i> {% trans "View Form Template" %}
|
||||
</a>
|
||||
<a href="{% url 'form_builder' job.form_template.slug %}" class="btn btn-outline-secondary w-100">
|
||||
<i class="fas fa-list-alt me-1"></i> {% trans "Manage Form Template" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<p>{% trans "This job status is not active, the form will appear once the job is made active"%}</p>
|
||||
{% endif %}
|
||||
|
||||
@ -446,7 +449,7 @@
|
||||
<div class="row g-3 stats-grid">
|
||||
|
||||
{# 1. Job Avg. Score #}
|
||||
<div class="col-6">
|
||||
<div class="col-4">
|
||||
<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>
|
||||
@ -457,7 +460,7 @@
|
||||
</div>
|
||||
|
||||
{# 2. High Potential Count #}
|
||||
<div class="col-6">
|
||||
<div class="col-4">
|
||||
<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>
|
||||
@ -468,7 +471,7 @@
|
||||
</div>
|
||||
|
||||
{# 3. Avg. Time to Interview #}
|
||||
<div class="col-6">
|
||||
{% comment %} <div class="col-6">
|
||||
<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>
|
||||
@ -487,9 +490,9 @@
|
||||
<small class="text-muted d-block">{% trans "Avg. Exam Review" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
<!--Vacancy fill rate-->
|
||||
<div class="col-6">
|
||||
<div class="col-4">
|
||||
<div class="card text-center h-100">
|
||||
<div class="card-body p-2">
|
||||
<i class="fas fa-trophy text-secondary mb-1 d-block" style="font-size: 1.2rem;"></i>
|
||||
|
||||
@ -325,7 +325,7 @@
|
||||
</td>
|
||||
|
||||
{# CANDIDATE MANAGEMENT DATA - URLS NEUTRALIZED #}
|
||||
<td class="candidate-data-cell text-primary-theme"><a href="#" class="text-primary-theme">{% if job.all_candidates.count %}{{ job.all_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-primary-theme"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-primary-theme">{% if job.all_candidates.count %}{{ job.all_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-info"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-info">{% if job.screening_candidates.count %}{{ job.screening_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_exam_view' job.slug %}" class="text-success">{% if job.exam_candidates.count %}{{ job.exam_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_interview_view' job.slug %}" class="text-success">{% if job.interview_candidates.count %}{{ job.interview_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 px-3 py-3">
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-link me-2"></i>
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="kaauh-card shadow-sm mb-4">
|
||||
<div class="kaauh-card shadow-sm mb-4 px-3 py-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<h5 class="card-title mb-0">
|
||||
@ -77,7 +77,7 @@
|
||||
</div>
|
||||
|
||||
<div class="kaauh-card shadow-sm">
|
||||
<div class="card-body">
|
||||
<div class="card-body px-3 py-3">
|
||||
<h5 class="card-title mb-3">
|
||||
<i class="fas fa-key me-2 text-warning"></i>
|
||||
{% trans "Access Credentials" %}
|
||||
@ -125,7 +125,7 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<div class="kaauh-card shadow-sm mb-4">
|
||||
<div class="kaauh-card shadow-sm mb-4 px-3 py-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-3">
|
||||
<i class="fas fa-chart-line me-2 text-info"></i>
|
||||
@ -161,7 +161,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kaauh-card shadow-sm">
|
||||
<div class="kaauh-card shadow-sm px-3 py-3">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title mb-3">
|
||||
<i class="fas fa-cog me-2 text-secondary"></i>
|
||||
|
||||
@ -473,7 +473,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<div class="card kaauh-card mb-4">
|
||||
{% comment %} <div class="card kaauh-card mb-4">
|
||||
<div class="card-header bg-white border-bottom">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark);">
|
||||
<i class="fas fa-bolt me-2"></i>
|
||||
@ -503,7 +503,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
|
||||
<!-- Agency Information -->
|
||||
<div class="card kaauh-card">
|
||||
|
||||
@ -417,15 +417,16 @@
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if candidate.interview_date %}
|
||||
|
||||
{% if candidate.get_interview_date %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-comments"></i></div>
|
||||
<div class="timeline-content">
|
||||
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Interview" %}</p>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.interview_date|date:"M d, Y" }}
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.get_interview_date}}
|
||||
<span class="ms-2">|</span>
|
||||
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.interview_date|date:"h:i A" }}
|
||||
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.get_interview_time}}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@ -439,8 +440,21 @@
|
||||
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.offer_date|date:"M d, Y" }}
|
||||
<span class="ms-2">|</span>
|
||||
<i class="far fa-clock ms-2 me-1"></i> {{ candidate.offer_date|date:"h:i A" }}
|
||||
|
||||
</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if candidate.hired_date %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-icon timeline-bg-applied"><i class="fas fas fa-handshake"></i></div>
|
||||
<div class="timeline-content">
|
||||
<p class="timeline-stage fw-bold mb-0 ms-2">{% trans "Offer" %}</p>
|
||||
<small class="text-muted">
|
||||
<i class="far fa-calendar-alt me-1"></i> {{ candidate.hired_date|date:"M d, Y" }}
|
||||
|
||||
</small>
|
||||
</div>
|
||||
|
||||
@ -651,7 +665,16 @@
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm mb-4 p-2">
|
||||
<h5 class="text-muted mb-3"><i class="fas fa-clock me-2"></i>{% trans "Time to Hire: " %}{{candidate.time_to_hire|default:100}} days</h5>
|
||||
<h5 class="text-muted mb-3"><i class="fas fa-clock me-2"></i>{% trans "Time to Hire:" %}
|
||||
|
||||
{% with days=candidate.time_to_hire_days %}
|
||||
{% if days > 0 %}
|
||||
{{ days }} day{{ days|pluralize }}
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</h5>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@ -265,7 +265,7 @@
|
||||
<th style="width: 10%"><i class="fas fa-calendar me-1"></i> {% trans "Meeting Date" %}</th>
|
||||
<th style="width: 7%"><i class="fas fa-video me-1"></i> {% trans "Link" %}</th>
|
||||
<th style="width: 8%"><i class="fas fa-check-circle me-1"></i> {% trans "Meeting Status" %}</th>
|
||||
<th style="width: 5%"><i class="fas fa-check-circle me-1"></i> {% trans "Interview Result" %}</th>
|
||||
<th style="width: 5%"><i class="fas fa-check-circle me-1"></i> {% trans "Interview Result"%}</th>
|
||||
<th style="width: 10%"><i class="fas fa-cog me-1"></i> {% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -276,11 +276,20 @@
|
||||
<div class="form-check">
|
||||
<input name="candidate_ids" value="{{ candidate.id }}" type="checkbox" class="form-check-input rowCheckbox" id="candidate-{{ candidate.id }}">
|
||||
</div>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
<div class="candidate-name">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'candidate_criteria_view_htmx' candidate.pk %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
title="View Profile">
|
||||
{{ candidate.name }}<i class="fas fa-eye ms-1"></i>
|
||||
</button>
|
||||
{% comment %} <div class="candidate-name">
|
||||
{{ candidate.name }}
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
</td>
|
||||
<td>
|
||||
<div class="candidate-details">
|
||||
@ -365,14 +374,14 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
{% comment %} <button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#candidateviewModal"
|
||||
hx-get="{% url 'candidate_criteria_view_htmx' candidate.pk %}"
|
||||
hx-target="#candidateviewModalBody"
|
||||
title="View Profile">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</button> {% endcomment %}
|
||||
<button type="button" class="btn btn-outline-info btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#emailModal"
|
||||
@ -457,17 +466,14 @@
|
||||
|
||||
<form method="post" action="{% url 'candidate_interview_view' job.slug %}">
|
||||
{% csrf_token %}
|
||||
<<<<<<< HEAD
|
||||
|
||||
|
||||
<div class="modal-body table-responsive">
|
||||
=======
|
||||
|
||||
<div class="modal-body">
|
||||
>>>>>>> c6fcb276135dc7e87bb0d065a93ff89091ff0207
|
||||
{{ job.internal_job_id }} {{ job.title}}
|
||||
|
||||
<hr>
|
||||
<<<<<<< HEAD
|
||||
|
||||
|
||||
|
||||
<table class="table tab table-bordered mt-3">
|
||||
@ -490,18 +496,7 @@
|
||||
|
||||
|
||||
</table>
|
||||
=======
|
||||
|
||||
<h3>👥 {% trans "Participants" %}</h3>
|
||||
{{ form.participants.errors }}
|
||||
{{ form.participants }}
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>🧑💼 {% trans "Users" %}</h3>
|
||||
{{ form.users.errors }}
|
||||
{{ form.users }}
|
||||
>>>>>>> c6fcb276135dc7e87bb0d065a93ff89091ff0207
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
|
||||
@ -30,54 +30,113 @@
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Card Header and Icon Styling */
|
||||
/* ------------------------------------------------------------- */
|
||||
/* CONSOLIDATED CARD HEADER STYLES (for both main and stat cards)*/
|
||||
/* ------------------------------------------------------------- */
|
||||
.card-header {
|
||||
font-weight: 600;
|
||||
padding: 1.25rem;
|
||||
/* Consistent, reduced padding for compact look */
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom: 1px solid var(--kaauh-border);
|
||||
background-color: #f8f9fa;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.card-header h3, .card-header h2 {
|
||||
/* Target h2 (for main headers) and h3 (for stat card headers) */
|
||||
.card-header h1, .card-header h2, .card-header h3, .card-header h6 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--kaauh-primary-text);
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
|
||||
/* Font size for MAIN card titles (e.g., "Data Scope: All Jobs") */
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Override for H3 inside stat cards to make them very small */
|
||||
.stats .card-header h3 {
|
||||
font-size: 0.85rem; /* Smallest size for the 9-card layout */
|
||||
font-weight: 600;
|
||||
white-space: nowrap; /* Prevent title from wrapping */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
color: var(--kaauh-teal);
|
||||
font-size: 1.75rem;
|
||||
margin-right: 0.75rem;
|
||||
font-size: 0.8rem; /* Small icon size */
|
||||
margin-right: 0.25rem;
|
||||
/* Note: For 9-card density, you might still want to hide this on mobile */
|
||||
}
|
||||
|
||||
/* Stats Grid Layout */
|
||||
/* ------------------------------------------------------------- */
|
||||
/* STATS GRID LAYOUT (9 Columns) */
|
||||
/* ------------------------------------------------------------- */
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 3rem;
|
||||
/* Force 9 columns */
|
||||
grid-template-columns: repeat(9, 1fr);
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Stat Card Specific Styling */
|
||||
.stat-value {
|
||||
font-size: 2.8rem;
|
||||
/* This is the most important number */
|
||||
font-size: 1.5rem; /* Increased slightly for focus, was 1rem/1.25rem */
|
||||
text-align: center;
|
||||
color: var(--kaauh-teal-dark);
|
||||
font-weight: 700;
|
||||
padding: 1rem 1rem 0.5rem;
|
||||
font-weight: 700;
|
||||
padding: 0.5rem 0.25rem 0.1rem; /* Very little bottom padding */
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.stat-caption {
|
||||
font-size: 0.9rem;
|
||||
/* Smallest text for the label below the value */
|
||||
font-size: 0.7rem; /* Minimized size */
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
padding-bottom: 1rem;
|
||||
padding: 0 0.25rem 0.5rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------- */
|
||||
/* RESPONSIVE DESIGN */
|
||||
/* ------------------------------------------------------------- */
|
||||
|
||||
/* On tablets and smaller laptops (1200px and down) */
|
||||
@media (max-width: 1200px) {
|
||||
.stats {
|
||||
/* Switch to 4 columns on medium screens */
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 1.75rem; /* Increase value size slightly when more space is available */
|
||||
}
|
||||
.stat-caption {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* On phones (576px and down) */
|
||||
@media (max-width: 576px) {
|
||||
.stats {
|
||||
/* Stack to 2 columns on mobile for readability */
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.stat-value {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.stat-caption {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dropdown/Filter Styling */
|
||||
@ -130,7 +189,7 @@
|
||||
<h2>
|
||||
<i class="fas fa-search stat-icon"></i>
|
||||
{% if current_job %}
|
||||
{% trans "Data Scope: " %} **{{ current_job.title }}**
|
||||
{% trans "Data Scope: " %}{{ current_job.title }}
|
||||
{% else %}
|
||||
{% trans "Data Scope: All Jobs" %}
|
||||
{% endif %}
|
||||
@ -153,9 +212,7 @@
|
||||
{# STATS CARDS SECTION (12 KPIs) #}
|
||||
{# -------------------------------------------------------------------------- #}
|
||||
{% include 'recruitment/partials/stats_cards.html' %}
|
||||
|
||||
|
||||
|
||||
{# Note: The content of 'recruitment/partials/stats_cards.html' uses h3 which is styled correctly here #}
|
||||
|
||||
{# -------------------------------------------------------------------------- #}
|
||||
{# CHARTS SECTION #}
|
||||
@ -197,7 +254,7 @@
|
||||
<h2>
|
||||
<i class="fas fa-funnel-dollar stat-icon"></i>
|
||||
{% if current_job %}
|
||||
{% trans "Pipeline Funnel: " %} **{{ current_job.title }}**
|
||||
{% trans "Pipeline Funnel: " %}{{ current_job.title }}
|
||||
{% else %}
|
||||
{% trans "Total Pipeline Funnel (All Jobs)" %}
|
||||
{% endif %}
|
||||
@ -223,28 +280,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="card shadow-sm no-hover mb-4">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-chart-pie me-2 text-primary"></i>
|
||||
{% trans "Candidates From Each Sources" %}
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<div style="height: 300px;">
|
||||
<canvas id="candidatesourceschart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/luxon@3.4.4"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.3.1"></script>
|
||||
|
||||
|
||||
<script>
|
||||
// Pass context data safely to JavaScript
|
||||
const totalCandidatesScoped = parseInt('{{ total_candidates|default:0 }}');
|
||||
@ -462,80 +504,7 @@
|
||||
|
||||
|
||||
|
||||
// Chart for Candidate Categories and Match Scores
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const ctx = document.getElementById('candidatesourceschart');
|
||||
if (!ctx) {
|
||||
console.warn('Candidates sources chart element not found.');
|
||||
return;
|
||||
}
|
||||
const chartCtx = ctx.getContext('2d');
|
||||
|
||||
// Safely get job_category_data from Django context
|
||||
// Using window.jobChartData to avoid template parsing issues
|
||||
|
||||
|
||||
if (categories.length > 0) { // Only render if there's data
|
||||
const chart = new Chart(chartCtx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: categories,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Number of Candidates',
|
||||
data: candidates_count_in_each_source,
|
||||
backgroundColor: [
|
||||
'rgba(0, 99, 110, 0.7)', // --kaauh-teal
|
||||
'rgba(23, 162, 184, 0.7)', // Teal shade
|
||||
'rgba(0, 150, 136, 0.7)', // Teal green
|
||||
'rgba(0, 188, 212, 0.7)', // Cyan
|
||||
'rgba(38, 166, 154, 0.7)', // Turquoise
|
||||
'rgba(77, 182, 172, 0.7)', // Medium teal
|
||||
// Add more colors if you expect more categories
|
||||
],
|
||||
borderColor: [
|
||||
'rgba(0, 99, 110, 1)',
|
||||
'rgba(23, 162, 184, 1)',
|
||||
'rgba(0, 150, 136, 1)',
|
||||
'rgba(0, 188, 212, 1)',
|
||||
'rgba(38, 166, 154, 1)',
|
||||
'rgba(77, 182, 172, 1)',
|
||||
// Add more colors if you expect more categories
|
||||
],
|
||||
borderWidth: 1,
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false, // Important for fixed height container
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'right', // Position legend for doughnut chart
|
||||
},
|
||||
title: {
|
||||
display: false, // Chart title is handled by the card header
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
let label = context.label || '';
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
label += context.parsed + ' candidate(s)';
|
||||
return label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Display a message if no data is available
|
||||
chartCtx.canvas.parentNode.innerHTML = '<p class="text-center text-muted mt-4">No candidate category data available for this job.</p>';
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
@ -10,7 +10,7 @@
|
||||
<h3><i class="fas fa-list stat-icon"></i> {% trans "Total Jobs" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ total_jobs_global }}</div>
|
||||
<div class="stat-caption">{% trans "All Active & Drafted Positions (Global)" %}</div>
|
||||
<div class="stat-caption">{% trans "All Active & Drafted Positions" %}</div>
|
||||
</div>
|
||||
|
||||
{# SCOPED - 2. Total Active Jobs #}
|
||||
@ -19,7 +19,7 @@
|
||||
<h3><i class="fas fa-briefcase stat-icon"></i> {% trans "Active Jobs" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ total_active_jobs }}</div>
|
||||
<div class="stat-caption">{% trans "Currently Open Requisitions (Scoped)" %}</div>
|
||||
<div class="stat-caption">{% trans "Currently Open Requisitions" %}</div>
|
||||
</div>
|
||||
|
||||
{# SCOPED - 3. Total Candidates #}
|
||||
@ -28,7 +28,7 @@
|
||||
<h3><i class="fas fa-users stat-icon"></i> {% trans "Total Candidates" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ total_candidates }}</div>
|
||||
<div class="stat-caption">{% trans "Total Profiles in Current Scope" %}</div>
|
||||
<div class="stat-caption">{% trans "Total applications" %}</div>
|
||||
</div>
|
||||
|
||||
{# SCOPED - 4. Open Positions #}
|
||||
@ -37,7 +37,7 @@
|
||||
<h3><i class="fas fa-th-list stat-icon"></i> {% trans "Open Positions" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ total_open_positions }}</div>
|
||||
<div class="stat-caption">{% trans "Total Slots to be Filled (Scoped)" %}</div>
|
||||
<div class="stat-caption">{% trans "Total Slots to be Filled " %}</div>
|
||||
</div>
|
||||
|
||||
{# GLOBAL - 5. Total Participants #}
|
||||
@ -46,11 +46,11 @@
|
||||
<h3><i class="fas fa-address-book stat-icon"></i> {% trans "Total Participants" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ total_participants }}</div>
|
||||
<div class="stat-caption">{% trans "Total Recruiters/Interviewers (Global)" %}</div>
|
||||
<div class="stat-caption">{% trans "Total Recruiters/Interviewers" %}</div>
|
||||
</div>
|
||||
|
||||
{# GLOBAL - 6. Total LinkedIn Posts #}
|
||||
<div class="card">
|
||||
{% comment %} <div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fab fa-linkedin stat-icon"></i> {% trans "LinkedIn Posts" %}</h3>
|
||||
</div>
|
||||
@ -65,13 +65,13 @@
|
||||
<div class="stat-value">{{ new_candidates_7days }}</div>
|
||||
<div class="stat-caption">{% trans "Incoming applications last week" %}</div>
|
||||
</div>
|
||||
|
||||
{% endcomment %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-cogs stat-icon"></i> {% trans "Avg. Apps per Job" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ average_applications|floatformat:1 }}</div>
|
||||
<div class="stat-caption">{% trans "Average Applications per Job (Scoped)" %}</div>
|
||||
<div class="stat-caption">{% trans "Average Applications per Job" %}</div>
|
||||
</div>
|
||||
|
||||
{# --- Efficiency & Quality Metrics --- #}
|
||||
@ -81,7 +81,7 @@
|
||||
<h3><i class="fas fa-clock stat-icon"></i> {% trans "Time-to-Hire" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ avg_time_to_hire_days }}</div>
|
||||
<div class="stat-caption">{% trans "Avg. Days (Application to Hired)" %}</div>
|
||||
<div class="stat-caption">{% trans "Average Days" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@ -89,7 +89,7 @@
|
||||
<h3><i class="fas fa-star stat-icon"></i> {% trans "Avg. Match Score" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ avg_match_score|floatformat:1 }}</div>
|
||||
<div class="stat-caption">{% trans "Average AI Score (Current Scope)" %}</div>
|
||||
<div class="stat-caption">{% trans "Average AI Score " %}</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@ -100,13 +100,13 @@
|
||||
<div class="stat-caption">{% trans "Score ≥ 75% Profiles" %} ({{ high_potential_ratio|floatformat:1 }}%)</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
{% comment %} <div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-calendar-alt stat-icon"></i> {% trans "Meetings This Week" %}</h3>
|
||||
</div>
|
||||
<div class="stat-value">{{ meetings_scheduled_this_week }}</div>
|
||||
<div class="stat-caption">{% trans "Scheduled Interviews (Current Week)" %}</div>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@ -146,7 +146,7 @@
|
||||
<div class="row px-lg-4">
|
||||
<div class="col-12">
|
||||
<h1 class="h3 fw-bold dashboard-header">
|
||||
<i class="fas fa-cogs me-3 text-accent"></i>{% trans "Admin Settings Dashboard" %}
|
||||
<i class="fas fa-cogs me-3 text-accent"></i>{% trans "Staff Management Dashboard" %}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
@ -165,6 +165,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-12 table-card">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover align-middle mb-0">
|
||||
@ -180,6 +181,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{% for user in staffs %}
|
||||
<tr>
|
||||
<td>{{ user.pk }}</td>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user