Merge branch 'main' of http://10.10.1.136:3000/marwan/kaauh_ats into frontend
This commit is contained in:
commit
5b114b630e
6
.env
6
.env
@ -1,3 +1,3 @@
|
|||||||
DB_NAME=haikal_db
|
DB_NAME=norahuniversity
|
||||||
DB_USER=faheed
|
DB_USER=norahuniversity
|
||||||
DB_PASSWORD=Faheed@215
|
DB_PASSWORD=norahuniversity
|
||||||
9882
django.po.bkp
Normal file
9882
django.po.bkp
Normal file
File diff suppressed because it is too large
Load Diff
9885
django2.po
Normal file
9885
django2.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,7 @@ class ERPIntegrationService:
|
|||||||
Validate the incoming request from ERP system
|
Validate the incoming request from ERP system
|
||||||
Returns: (is_valid, error_message)
|
Returns: (is_valid, error_message)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check if source is active
|
# Check if source is active
|
||||||
if not self.source.is_active:
|
if not self.source.is_active:
|
||||||
return False, "Source is not active"
|
return False, "Source is not active"
|
||||||
@ -70,6 +71,7 @@ class ERPIntegrationService:
|
|||||||
try:
|
try:
|
||||||
# Map ERP fields to JobPosting fields
|
# Map ERP fields to JobPosting fields
|
||||||
job_data = {
|
job_data = {
|
||||||
|
'internal_job_id': request_data.get('job_id', '').strip(),
|
||||||
'title': request_data.get('title', '').strip(),
|
'title': request_data.get('title', '').strip(),
|
||||||
'department': request_data.get('department', '').strip(),
|
'department': request_data.get('department', '').strip(),
|
||||||
'job_type': self.map_job_type(request_data.get('job_type', 'FULL_TIME')),
|
'job_type': self.map_job_type(request_data.get('job_type', 'FULL_TIME')),
|
||||||
|
|||||||
@ -269,7 +269,7 @@ class SourceAdvancedForm(forms.ModelForm):
|
|||||||
class PersonForm(forms.ModelForm):
|
class PersonForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Person
|
model = Person
|
||||||
fields = ["first_name","middle_name", "last_name", "email", "phone","date_of_birth","nationality","address","gender"]
|
fields = ["first_name","middle_name", "last_name", "email", "phone","date_of_birth","nationality","gender","address"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"first_name": forms.TextInput(attrs={'class': 'form-control'}),
|
"first_name": forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
"middle_name": forms.TextInput(attrs={'class': 'form-control'}),
|
"middle_name": forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
@ -591,7 +591,6 @@ class JobPostingForm(forms.ModelForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"class": "form-control",
|
"class": "form-control",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"placeholder": "Maximum number of applicants",
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -834,9 +833,9 @@ class ProfileImageUploadForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class StaffUserCreationForm(UserCreationForm):
|
class StaffUserCreationForm(UserCreationForm):
|
||||||
email = forms.EmailField(required=True)
|
email = forms.EmailField(label=_("Email"), required=True)
|
||||||
first_name = forms.CharField(max_length=30, required=True)
|
first_name = forms.CharField(label=_("First Name"),max_length=30, required=True)
|
||||||
last_name = forms.CharField(max_length=150, required=True)
|
last_name = forms.CharField(label=_("Last Name"),max_length=150, required=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
@ -1078,7 +1077,6 @@ class AgencyJobAssignmentForm(forms.ModelForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"class": "form-control",
|
"class": "form-control",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"placeholder": "Maximum number of candidates",
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
"deadline_date": forms.DateTimeInput(
|
"deadline_date": forms.DateTimeInput(
|
||||||
@ -1090,7 +1088,6 @@ class AgencyJobAssignmentForm(forms.ModelForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"class": "form-control",
|
"class": "form-control",
|
||||||
"rows": 3,
|
"rows": 3,
|
||||||
"placeholder": "Internal notes about this assignment",
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
18
recruitment/migrations/0006_alter_customuser_email.py
Normal file
18
recruitment/migrations/0006_alter_customuser_email.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.6 on 2025-11-23 12:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('recruitment', '0005_alter_interviewschedule_template_location'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(error_messages={'unique': 'A user with this email already exists.'}, max_length=254, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -47,6 +47,12 @@ class CustomUser(AbstractUser):
|
|||||||
designation = models.CharField(
|
designation = models.CharField(
|
||||||
max_length=100, blank=True, null=True, verbose_name=_("Designation")
|
max_length=100, blank=True, null=True, verbose_name=_("Designation")
|
||||||
)
|
)
|
||||||
|
email = models.EmailField(
|
||||||
|
unique=True,
|
||||||
|
error_messages={
|
||||||
|
"unique": _("A user with this email already exists."),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("User")
|
verbose_name = _("User")
|
||||||
@ -247,7 +253,7 @@ class JobPosting(Base):
|
|||||||
)
|
)
|
||||||
# Field to store the generated zip file
|
# Field to store the generated zip file
|
||||||
cv_zip_file = models.FileField(upload_to='job_zips/', null=True, blank=True)
|
cv_zip_file = models.FileField(upload_to='job_zips/', null=True, blank=True)
|
||||||
|
|
||||||
# Field to track if the background task has completed
|
# Field to track if the background task has completed
|
||||||
zip_created = models.BooleanField(default=False)
|
zip_created = models.BooleanField(default=False)
|
||||||
|
|
||||||
@ -487,7 +493,6 @@ class Person(Base):
|
|||||||
unique=True,
|
unique=True,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
verbose_name=_("Email"),
|
verbose_name=_("Email"),
|
||||||
help_text=_("Unique email address for the person"),
|
|
||||||
)
|
)
|
||||||
phone = models.CharField(
|
phone = models.CharField(
|
||||||
max_length=20, blank=True, null=True, verbose_name=_("Phone")
|
max_length=20, blank=True, null=True, verbose_name=_("Phone")
|
||||||
@ -1068,14 +1073,14 @@ class Application(Base):
|
|||||||
|
|
||||||
content_type = ContentType.objects.get_for_model(self.__class__)
|
content_type = ContentType.objects.get_for_model(self.__class__)
|
||||||
return Document.objects.filter(content_type=content_type, object_id=self.id)
|
return Document.objects.filter(content_type=content_type, object_id=self.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def belong_to_an_agency(self):
|
def belong_to_an_agency(self):
|
||||||
if self.hiring_agency:
|
if self.hiring_agency:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
deadline=self.job.application_deadline
|
deadline=self.job.application_deadline
|
||||||
@ -1084,7 +1089,7 @@ class Application(Base):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -1253,7 +1258,7 @@ class OnsiteLocationDetails(InterviewLocation):
|
|||||||
verbose_name_plural = _("Onsite Location Details")
|
verbose_name_plural = _("Onsite Location Details")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# --- 2. Scheduling Models ---
|
# --- 2. Scheduling Models ---
|
||||||
|
|||||||
@ -984,7 +984,7 @@ from django.utils.html import strip_tags
|
|||||||
|
|
||||||
def _task_send_individual_email(subject, body_message, recipient, attachments,sender,job):
|
def _task_send_individual_email(subject, body_message, recipient, attachments,sender,job):
|
||||||
"""Internal helper to create and send a single email."""
|
"""Internal helper to create and send a single email."""
|
||||||
|
|
||||||
|
|
||||||
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa')
|
from_email = getattr(settings, 'DEFAULT_FROM_EMAIL', 'noreply@kaauh.edu.sa')
|
||||||
is_html = '<' in body_message and '>' in body_message
|
is_html = '<' in body_message and '>' in body_message
|
||||||
@ -1006,7 +1006,7 @@ def _task_send_individual_email(subject, body_message, recipient, attachments,se
|
|||||||
result=email_obj.send(fail_silently=False)
|
result=email_obj.send(fail_silently=False)
|
||||||
|
|
||||||
if result==1 and sender and job: # job is none when email sent after message creation
|
if result==1 and sender and job: # job is none when email sent after message creation
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user=get_object_or_404(User,email=recipient)
|
user=get_object_or_404(User,email=recipient)
|
||||||
new_message = Message.objects.create(
|
new_message = Message.objects.create(
|
||||||
@ -1086,7 +1086,7 @@ def generate_and_save_cv_zip(job_posting_id):
|
|||||||
"""
|
"""
|
||||||
job = JobPosting.objects.get(id=job_posting_id)
|
job = JobPosting.objects.get(id=job_posting_id)
|
||||||
entries = Application.objects.filter(job=job)
|
entries = Application.objects.filter(job=job)
|
||||||
|
|
||||||
zip_buffer = io.BytesIO()
|
zip_buffer = io.BytesIO()
|
||||||
|
|
||||||
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
|
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
|
||||||
@ -1112,7 +1112,7 @@ def generate_and_save_cv_zip(job_posting_id):
|
|||||||
zip_buffer.seek(0)
|
zip_buffer.seek(0)
|
||||||
now = str(timezone.now())
|
now = str(timezone.now())
|
||||||
zip_filename = f"all_cvs_for_{job.slug}_{job.title}_{now}.zip"
|
zip_filename = f"all_cvs_for_{job.slug}_{job.title}_{now}.zip"
|
||||||
|
|
||||||
# Use ContentFile to save the bytes stream into the FileField
|
# Use ContentFile to save the bytes stream into the FileField
|
||||||
job.cv_zip_file.save(zip_filename, ContentFile(zip_buffer.read()))
|
job.cv_zip_file.save(zip_filename, ContentFile(zip_buffer.read()))
|
||||||
job.zip_created = True # Assuming you added a BooleanField for tracking completion
|
job.zip_created = True # Assuming you added a BooleanField for tracking completion
|
||||||
|
|||||||
@ -187,7 +187,6 @@ class PersonCreateView(CreateView):
|
|||||||
template_name = "people/create_person.html"
|
template_name = "people/create_person.html"
|
||||||
form_class = PersonForm
|
form_class = PersonForm
|
||||||
success_url = reverse_lazy("person_list")
|
success_url = reverse_lazy("person_list")
|
||||||
print("from agency")
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if "HX-Request" in self.request.headers:
|
if "HX-Request" in self.request.headers:
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
@ -196,7 +195,6 @@ class PersonCreateView(CreateView):
|
|||||||
slug = self.request.POST.get("agency")
|
slug = self.request.POST.get("agency")
|
||||||
if slug:
|
if slug:
|
||||||
agency = HiringAgency.objects.get(slug=slug)
|
agency = HiringAgency.objects.get(slug=slug)
|
||||||
print(agency)
|
|
||||||
instance.agency = agency
|
instance.agency = agency
|
||||||
instance.save()
|
instance.save()
|
||||||
return redirect("agency_portal_persons_list")
|
return redirect("agency_portal_persons_list")
|
||||||
@ -826,11 +824,11 @@ def kaauh_career(request):
|
|||||||
selected_job_type = request.GET.get("employment_type", "")
|
selected_job_type = request.GET.get("employment_type", "")
|
||||||
|
|
||||||
job_type_keys = active_jobs.order_by("job_type").distinct("job_type").values_list("job_type", flat=True)
|
job_type_keys = active_jobs.order_by("job_type").distinct("job_type").values_list("job_type", flat=True)
|
||||||
|
|
||||||
workplace_type_keys = active_jobs.order_by("workplace_type").distinct("workplace_type").values_list(
|
workplace_type_keys = active_jobs.order_by("workplace_type").distinct("workplace_type").values_list(
|
||||||
"workplace_type", flat=True
|
"workplace_type", flat=True
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
if selected_job_type and selected_job_type in job_type_keys:
|
if selected_job_type and selected_job_type in job_type_keys:
|
||||||
active_jobs = active_jobs.filter(job_type=selected_job_type)
|
active_jobs = active_jobs.filter(job_type=selected_job_type)
|
||||||
if selected_workplace_type and selected_workplace_type in workplace_type_keys:
|
if selected_workplace_type and selected_workplace_type in workplace_type_keys:
|
||||||
@ -867,7 +865,10 @@ def kaauh_career(request):
|
|||||||
# job detail facing the candidate:
|
# job detail facing the candidate:
|
||||||
def application_detail(request, slug):
|
def application_detail(request, slug):
|
||||||
job = get_object_or_404(JobPosting, slug=slug)
|
job = get_object_or_404(JobPosting, slug=slug)
|
||||||
return render(request, "applicant/application_detail.html", {"job": job})
|
already_applied = False
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
already_applied = Application.objects.filter(job=job,person=request.user.person_profile).exists()
|
||||||
|
return render(request, "applicant/application_detail.html", {"job": job,"already_applied":already_applied})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -1202,7 +1203,13 @@ def application_submit_form(request, template_slug):
|
|||||||
"""Display the form as a step-by-step wizard"""
|
"""Display the form as a step-by-step wizard"""
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return redirect("candidate_signup",slug=template_slug)
|
return redirect("candidate_signup",slug=template_slug)
|
||||||
|
|
||||||
template = get_object_or_404(FormTemplate, slug=template_slug, is_active=True)
|
template = get_object_or_404(FormTemplate, slug=template_slug, is_active=True)
|
||||||
|
|
||||||
|
if Application.objects.filter(job=template.job,person=request.user.person_profile).exists():
|
||||||
|
messages.error(request, _("You have already submitted an application for this job."))
|
||||||
|
return redirect("application_detail",slug=template.job.slug)
|
||||||
|
|
||||||
stage = template.stages.filter(name="Contact Information")
|
stage = template.stages.filter(name="Contact Information")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -162,7 +162,7 @@ class ApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
|
|||||||
Q(person__first_name__icontains=search_query) |
|
Q(person__first_name__icontains=search_query) |
|
||||||
Q(person__last_name__icontains=search_query) |
|
Q(person__last_name__icontains=search_query) |
|
||||||
Q(person__email__icontains=search_query) |
|
Q(person__email__icontains=search_query) |
|
||||||
Q(person__phone__icontains=search_query)
|
Q(person__phone__icontains=search_query)
|
||||||
)
|
)
|
||||||
if job:
|
if job:
|
||||||
queryset = queryset.filter(job__slug=job)
|
queryset = queryset.filter(job__slug=job)
|
||||||
@ -202,11 +202,14 @@ class ApplicationCreateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessa
|
|||||||
job = get_object_or_404(models.JobPosting, slug=self.kwargs['slug'])
|
job = get_object_or_404(models.JobPosting, slug=self.kwargs['slug'])
|
||||||
form.instance.job = job
|
form.instance.job = job
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
def form_invalid(self, form):
|
||||||
|
messages.error(self.request, f"{form.errors.as_text()}")
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if self.request.method == 'GET':
|
# if self.request.method == 'GET':
|
||||||
context['person_form'] = forms.PersonForm()
|
context['person_form'] = forms.PersonForm()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class ApplicationUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView):
|
class ApplicationUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||||
@ -469,9 +472,9 @@ def dashboard_view(request):
|
|||||||
# NullIf(
|
# NullIf(
|
||||||
# # 2. Use Replace to remove the literal double quotes (") that might be present.
|
# # 2. Use Replace to remove the literal double quotes (") that might be present.
|
||||||
# Replace(
|
# Replace(
|
||||||
# # 1. Use the double-underscore path (which uses the ->> operator for the final value)
|
# # 1. Use the double-underscore path (which uses the ->> operator for the final value)
|
||||||
# # and cast to CharField for text-based cleanup functions.
|
# # and cast to CharField for text-based cleanup functions.
|
||||||
# Cast(SCORE_PATH, output_field=CharField()),
|
# Cast(SCORE_PATH, output_field=CharField()),
|
||||||
# Value('"'), Value('') # Replace the double quote character with an empty string
|
# Value('"'), Value('') # Replace the double quote character with an empty string
|
||||||
# ),
|
# ),
|
||||||
# Value('') # Value to check for (empty string)
|
# Value('') # Value to check for (empty string)
|
||||||
@ -479,10 +482,10 @@ def dashboard_view(request):
|
|||||||
# output_field=IntegerField() # 4. Cast the clean, non-empty string (or NULL) to an integer.
|
# output_field=IntegerField() # 4. Cast the clean, non-empty string (or NULL) to an integer.
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
# candidates_with_score_query= candidate_queryset.filter(is_resume_parsed=True).annotate(
|
# candidates_with_score_query= candidate_queryset.filter(is_resume_parsed=True).annotate(
|
||||||
# # The Coalesce handles NULL values (from missing data, non-numeric data, or NullIf) and sets them to 0.
|
# # The Coalesce handles NULL values (from missing data, non-numeric data, or NullIf) and sets them to 0.
|
||||||
# annotated_match_score=Coalesce(safe_match_score_cast, Value(0))
|
# annotated_match_score=Coalesce(safe_match_score_cast, Value(0))
|
||||||
# )
|
# )
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -81,6 +81,17 @@ class ERPIntegrationView(View):
|
|||||||
'message': 'Source not found'
|
'message': 'Source not found'
|
||||||
}, status=404)
|
}, status=404)
|
||||||
|
|
||||||
|
job_id = data.get('job_id')
|
||||||
|
if not job_id:
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Job ID is required and must be unique'
|
||||||
|
})
|
||||||
|
if JobPosting.objects.filter(internal_job_id=job_id).exists():
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Job with this ID already exists'
|
||||||
|
}, status=400)
|
||||||
# Create integration service
|
# Create integration service
|
||||||
service = ERPIntegrationService(source)
|
service = ERPIntegrationService(source)
|
||||||
|
|
||||||
@ -144,6 +155,7 @@ class ERPIntegrationView(View):
|
|||||||
def _create_job(self, service: ERPIntegrationService, data: Dict[str, Any]) -> tuple[Dict[str, Any], str]:
|
def _create_job(self, service: ERPIntegrationService, data: Dict[str, Any]) -> tuple[Dict[str, Any], str]:
|
||||||
"""Create a new job from ERP data"""
|
"""Create a new job from ERP data"""
|
||||||
# Validate ERP data
|
# Validate ERP data
|
||||||
|
# print(data)
|
||||||
is_valid, error_msg = service.validate_erp_data(data)
|
is_valid, error_msg = service.validate_erp_data(data)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
return None, error_msg
|
return None, error_msg
|
||||||
@ -152,7 +164,6 @@ class ERPIntegrationView(View):
|
|||||||
job, error_msg = service.create_job_from_erp(data)
|
job, error_msg = service.create_job_from_erp(data)
|
||||||
if error_msg:
|
if error_msg:
|
||||||
return None, error_msg
|
return None, error_msg
|
||||||
|
|
||||||
# Prepare response data
|
# Prepare response data
|
||||||
response_data = {
|
response_data = {
|
||||||
'job_id': job.internal_job_id,
|
'job_id': job.internal_job_id,
|
||||||
|
|||||||
@ -2,31 +2,31 @@
|
|||||||
{% load static i18n %}
|
{% load static i18n %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
{# ------------------------------------------------ #}
|
{# ------------------------------------------------ #}
|
||||||
{# 🚀 TOP NAV BAR (Sticky and Themed) #}
|
{# 🚀 TOP NAV BAR (Sticky and Themed) #}
|
||||||
{# ------------------------------------------------ #}
|
{# ------------------------------------------------ #}
|
||||||
<nav id="bottomNavbar" class="navbar navbar-expand-lg sticky-top border-bottom"
|
<nav id="bottomNavbar" class="navbar navbar-expand-lg sticky-top border-bottom"
|
||||||
style="background-color: var(--kaauh-teal); z-index: 1030; height: 50px;">
|
style="background-color: var(--kaauh-teal); z-index: 1030; height: 50px;">
|
||||||
<div class="container-fluid container-lg">
|
<div class="container-fluid container-lg">
|
||||||
<span class="navbar-text text-white fw-bold fs-6">{% trans "Job Overview" %}</span>
|
<span class="navbar-text text-white fw-bold fs-6">{% trans "Job Overview" %}</span>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{# ------------------------------------------------ #}
|
{# ------------------------------------------------ #}
|
||||||
{# 🔔 DJANGO MESSAGES (Refined placement and styling) #}
|
{# 🔔 DJANGO MESSAGES (Refined placement and styling) #}
|
||||||
{# ------------------------------------------------ #}
|
{# ------------------------------------------------ #}
|
||||||
|
|
||||||
|
|
||||||
{# ------------------------------------------------ #}
|
{# ------------------------------------------------ #}
|
||||||
{# 💻 MAIN CONTENT CONTAINER #}
|
{# 💻 MAIN CONTENT CONTAINER #}
|
||||||
{# ------------------------------------------------ #}
|
{# ------------------------------------------------ #}
|
||||||
<div class="container mt-4 mb-5">
|
<div class="container mt-4 mb-5">
|
||||||
<div class="row g-4 main-content-area">
|
<div class="row g-4 main-content-area">
|
||||||
|
|
||||||
{# 📌 RIGHT COLUMN: Sticky Apply Card (Desktop Only) #}
|
{# 📌 RIGHT COLUMN: Sticky Apply Card (Desktop Only) #}
|
||||||
<div class="col-lg-4 order-lg-2 d-none d-lg-block">
|
<div class="col-lg-4 order-lg-2 d-none d-lg-block">
|
||||||
<div class="card shadow-lg border-0" style="position: sticky; top: 70px;">
|
<div class="card shadow-lg border-0" style="position: sticky; top: 70px;">
|
||||||
<div class="card-header bg-white border-bottom p-3">
|
<div class="card-header bg-white border-bottom p-3">
|
||||||
<h5 class="mb-0 fw-bold text-kaauh-teal">
|
<h5 class="mb-0 fw-bold text-kaauh-teal">
|
||||||
<i class="fas fa-file-signature me-2"></i>{% trans "Ready to Apply?" %}
|
<i class="fas fa-file-signature me-2"></i>{% trans "Ready to Apply?" %}
|
||||||
@ -34,14 +34,24 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body text-center p-4">
|
<div class="card-body text-center p-4">
|
||||||
<p class="text-muted small mb-3">{% trans "Review the full job details below before submitting your application." %}</p>
|
<p class="text-muted small mb-3">{% trans "Review the full job details below before submitting your application." %}</p>
|
||||||
|
|
||||||
{% if job.form_template %}
|
{% if job.form_template %}
|
||||||
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100 shadow-sm">
|
{% if user.is_authenticated and already_applied %}
|
||||||
|
<button class="btn btn-main-action btn-lg w-100" disabled>
|
||||||
|
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% comment %} <a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100 shadow-sm">
|
||||||
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
||||||
</a>
|
</a>
|
||||||
{% elif not job.is_expired %}
|
{% elif not job.is_expired %}
|
||||||
<p class="text-danger fw-bold">{% trans "Application form is unavailable." %}</p>
|
<p class="text-danger fw-bold">{% trans "Application form is unavailable." %}</p>
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -49,32 +59,32 @@
|
|||||||
{# 📝 LEFT COLUMN: Job Details #}
|
{# 📝 LEFT COLUMN: Job Details #}
|
||||||
<div class="col-lg-8 order-lg-1">
|
<div class="col-lg-8 order-lg-1">
|
||||||
<article class="card shadow-lg border-0">
|
<article class="card shadow-lg border-0">
|
||||||
|
|
||||||
{# Job Title Header #}
|
{# Job Title Header #}
|
||||||
<header class="card-header bg-white border-bottom p-4">
|
<header class="card-header bg-white border-bottom p-4">
|
||||||
<h1 class="h2 mb-0 fw-bolder text-kaauh-teal">{{ job.title }}</h1>
|
<h1 class="h2 mb-0 fw-bolder text-kaauh-teal">{{ job.title }}</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="card-body p-4">
|
<div class="card-body p-4">
|
||||||
|
|
||||||
<h4 class="mb-4 fw-bold text-muted border-bottom pb-2">{% trans "Summary" %}</h4>
|
<h4 class="mb-4 fw-bold text-muted border-bottom pb-2">{% trans "Summary" %}</h4>
|
||||||
|
|
||||||
{# Job Metadata/Overview Grid #}
|
{# Job Metadata/Overview Grid #}
|
||||||
<section class="row row-cols-1 row-cols-md-2 g-3 mb-5 small text-secondary p-3 rounded bg-light-subtle border">
|
<section class="row row-cols-1 row-cols-md-2 g-3 mb-5 small text-secondary p-3 rounded bg-light-subtle border">
|
||||||
|
|
||||||
{# SALARY #}
|
{# SALARY #}
|
||||||
{% if job.salary_range %}
|
{% if job.salary_range %}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<i class="fas fa-money-bill-wave text-success me-2 fa-fw"></i>
|
<i class="fas fa-money-bill-wave text-success me-2 fa-fw"></i>
|
||||||
<strong>{% trans "Salary:" %}</strong>
|
<strong>{% trans "Salary:" %}</strong>
|
||||||
<span class="fw-bold text-success">{{ job.salary_range }}</span>
|
<span class="fw-bold text-success">{{ job.salary_range }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# DEADLINE #}
|
{# DEADLINE #}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<i class="fas fa-calendar-alt text-muted me-2 fa-fw"></i>
|
<i class="fas fa-calendar-alt text-muted me-2 fa-fw"></i>
|
||||||
<strong>{% trans "Deadline:" %}</strong>
|
<strong>{% trans "Deadline:" %}</strong>
|
||||||
{% if job.application_deadline %}
|
{% if job.application_deadline %}
|
||||||
<time datetime="{{ job.application_deadline|date:'Y-m-d' }}">
|
<time datetime="{{ job.application_deadline|date:'Y-m-d' }}">
|
||||||
{{ job.application_deadline|date:"M d, Y" }}
|
{{ job.application_deadline|date:"M d, Y" }}
|
||||||
@ -86,50 +96,50 @@
|
|||||||
<span class="text-muted">{% trans "Ongoing" %}</span>
|
<span class="text-muted">{% trans "Ongoing" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# JOB TYPE #}
|
{# JOB TYPE #}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<i class="fas fa-briefcase text-muted me-2 fa-fw"></i>
|
<i class="fas fa-briefcase text-muted me-2 fa-fw"></i>
|
||||||
<strong>{% trans "Job Type:" %}</strong> {{ job.get_job_type_display }}
|
<strong>{% trans "Job Type:" %}</strong> {{ job.get_job_type_display }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# LOCATION #}
|
{# LOCATION #}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<i class="fas fa-map-marker-alt text-muted me-2 fa-fw"></i>
|
<i class="fas fa-map-marker-alt text-muted me-2 fa-fw"></i>
|
||||||
<strong>{% trans "Location:" %}</strong> {{ job.get_location_display }}
|
<strong>{% trans "Location:" %}</strong> {{ job.get_location_display }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# DEPARTMENT #}
|
{# DEPARTMENT #}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<i class="fas fa-building text-muted me-2 fa-fw"></i>
|
<i class="fas fa-building text-muted me-2 fa-fw"></i>
|
||||||
<strong>{% trans "Department:" %}</strong> {{ job.department|default:"N/A" }}
|
<strong>{% trans "Department:" %}</strong> {{ job.department|default:"N/A" }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# JOB ID #}
|
{# JOB ID #}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<i class="fas fa-hashtag text-muted me-2 fa-fw"></i>
|
<i class="fas fa-hashtag text-muted me-2 fa-fw"></i>
|
||||||
<strong>{% trans "JOB ID:" %}</strong> {{ job.internal_job_id|default:"N/A" }}
|
<strong>{% trans "JOB ID:" %}</strong> {{ job.internal_job_id|default:"N/A" }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# WORKPLACE TYPE #}
|
{# WORKPLACE TYPE #}
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<i class="fas fa-laptop-house text-muted me-2 fa-fw"></i>
|
<i class="fas fa-laptop-house text-muted me-2 fa-fw"></i>
|
||||||
<strong>{% trans "Workplace:" %}</strong> {{ job.get_workplace_type_display }}
|
<strong>{% trans "Workplace:" %}</strong> {{ job.get_workplace_type_display }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{# Detailed Accordion Section #}
|
{# Detailed Accordion Section #}
|
||||||
<div class="accordion accordion-flush" id="jobDetailAccordion">
|
<div class="accordion accordion-flush" id="jobDetailAccordion">
|
||||||
|
|
||||||
{% with active_collapse="collapseOne" %}
|
{% with active_collapse="collapseOne" %}
|
||||||
|
|
||||||
{# JOB DESCRIPTION #}
|
{# JOB DESCRIPTION #}
|
||||||
{% if job.has_description_content %}
|
{% if job.has_description_content %}
|
||||||
<div class="accordion-item border-top border-bottom">
|
<div class="accordion-item border-top border-bottom">
|
||||||
<h2 class="accordion-header" id="headingOne">
|
<h2 class="accordion-header" id="headingOne">
|
||||||
<button class="accordion-button fw-bold fs-5 text-kaauh-teal-dark" type="button"
|
<button class="accordion-button fw-bold fs-5 text-kaauh-teal-dark" type="button"
|
||||||
data-bs-toggle="collapse" data-bs-target="#{{ active_collapse }}" aria-expanded="true"
|
data-bs-toggle="collapse" data-bs-target="#{{ active_collapse }}" aria-expanded="true"
|
||||||
aria-controls="{{ active_collapse }}">
|
aria-controls="{{ active_collapse }}">
|
||||||
<i class="fas fa-info-circle me-3 fa-fw"></i> {% trans "Job Description" %}
|
<i class="fas fa-info-circle me-3 fa-fw"></i> {% trans "Job Description" %}
|
||||||
</button>
|
</button>
|
||||||
@ -141,12 +151,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# QUALIFICATIONS #}
|
{# QUALIFICATIONS #}
|
||||||
{% if job.has_qualifications_content %}
|
{% if job.has_qualifications_content %}
|
||||||
<div class="accordion-item border-bottom">
|
<div class="accordion-item border-bottom">
|
||||||
<h2 class="accordion-header" id="headingTwo">
|
<h2 class="accordion-header" id="headingTwo">
|
||||||
<button class="accordion-button collapsed fw-bold fs-5 text-kaauh-teal-dark" type="button"
|
<button class="accordion-button collapsed fw-bold fs-5 text-kaauh-teal-dark" type="button"
|
||||||
data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
data-bs-toggle="collapse" data-bs-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
|
||||||
<i class="fas fa-graduation-cap me-3 fa-fw"></i> {% trans "Qualifications" %}
|
<i class="fas fa-graduation-cap me-3 fa-fw"></i> {% trans "Qualifications" %}
|
||||||
</button>
|
</button>
|
||||||
@ -158,12 +168,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# BENEFITS #}
|
{# BENEFITS #}
|
||||||
{% if job.has_benefits_content %}
|
{% if job.has_benefits_content %}
|
||||||
<div class="accordion-item border-bottom">
|
<div class="accordion-item border-bottom">
|
||||||
<h2 class="accordion-header" id="headingThree">
|
<h2 class="accordion-header" id="headingThree">
|
||||||
<button class="accordion-button collapsed fw-bold fs-5 text-kaauh-teal-dark" type="button"
|
<button class="accordion-button collapsed fw-bold fs-5 text-kaauh-teal-dark" type="button"
|
||||||
data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
data-bs-toggle="collapse" data-bs-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
|
||||||
<i class="fas fa-hand-holding-usd me-3 fa-fw"></i> {% trans "Benefits" %}
|
<i class="fas fa-hand-holding-usd me-3 fa-fw"></i> {% trans "Benefits" %}
|
||||||
</button>
|
</button>
|
||||||
@ -180,7 +190,7 @@
|
|||||||
{% if job.has_application_instructions_content %}
|
{% if job.has_application_instructions_content %}
|
||||||
<div class="accordion-item border-bottom">
|
<div class="accordion-item border-bottom">
|
||||||
<h2 class="accordion-header" id="headingFour">
|
<h2 class="accordion-header" id="headingFour">
|
||||||
<button class="accordion-button collapsed fw-bold fs-5 text-kaauh-teal-dark" type="button"
|
<button class="accordion-button collapsed fw-bold fs-5 text-kaauh-teal-dark" type="button"
|
||||||
data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
|
data-bs-toggle="collapse" data-bs-target="#collapseFour" aria-expanded="false" aria-controls="collapseFour">
|
||||||
<i class="fas fa-file-alt me-3 fa-fw"></i> {% trans "Application Instructions" %}
|
<i class="fas fa-file-alt me-3 fa-fw"></i> {% trans "Application Instructions" %}
|
||||||
</button>
|
</button>
|
||||||
@ -192,25 +202,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# 📱 MOBILE FIXED APPLY BAR (Replaced inline style with utility classes) #}
|
{# 📱 MOBILE FIXED APPLY BAR (Replaced inline style with utility classes) #}
|
||||||
{% if job.form_template %}
|
{% if job.form_template %}
|
||||||
<footer class="fixed-bottom d-lg-none bg-white border-top shadow-lg p-3">
|
<footer class="fixed-bottom d-lg-none bg-white border-top shadow-lg p-3">
|
||||||
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100">
|
{% if user.is_authenticated and already_applied %}
|
||||||
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
<button class="btn btn-main-action btn-lg w-100" disabled>
|
||||||
</a>
|
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
|
||||||
</footer>
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</footer>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
{% endblock content%}
|
{% endblock content%}
|
||||||
@ -123,14 +123,16 @@
|
|||||||
</li> {% endcomment %}
|
</li> {% endcomment %}
|
||||||
<li class="nav-item me-2">
|
<li class="nav-item me-2">
|
||||||
{% if LANGUAGE_CODE == 'en' %}
|
{% if LANGUAGE_CODE == 'en' %}
|
||||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
<form action="{% url 'set_language' %}" method="post" class="d-inline">
|
||||||
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||||
<button name="language" value="ar" class="btn bg-primary-theme text-white" type="submit">
|
<button name="language" value="ar" class="btn bg-primary-theme text-white" type="submit">
|
||||||
<span class="me-2">🇸🇦</span> العربية
|
<span class="me-2">🇸🇦</span> العربية
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% elif LANGUAGE_CODE == 'ar' %}
|
{% elif LANGUAGE_CODE == 'ar' %}
|
||||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
<form action="{% url 'set_language' %}" method="post" class="d-inline">
|
||||||
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||||
<button name="language" value="en" class="btn bg-primary-theme text-white" type="submit">
|
<button name="language" value="en" class="btn bg-primary-theme text-white" type="submit">
|
||||||
<span class="me-2">🇺🇸</span> English
|
<span class="me-2">🇺🇸</span> English
|
||||||
@ -312,7 +314,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
{% comment %} <li class="nav-item dropdown ms-lg-2">
|
{% comment %} <li class="nav-item dropdown ms-lg-2">
|
||||||
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
|
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
|
||||||
data-bs-offset="0, 8" data-bs-auto-close="outside">
|
data-bs-offset="0, 8" data-bs-auto-close="outside">
|
||||||
|
|||||||
@ -1249,7 +1249,7 @@ const elements = {
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
state.templateId = result.template_slug;
|
state.templateId = result.template_slug;
|
||||||
window.location.href = "{% url 'form_templates_list' %}";
|
window.location.href = "{% url 'job_detail' template.job.slug %}";
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
alert('Error saving form template: ' + result.error);
|
alert('Error saving form template: ' + result.error);
|
||||||
|
|||||||
@ -331,15 +331,15 @@
|
|||||||
<a href="{% url 'candidate_screening_view' job.slug %}" class="btn btn-main-action">
|
<a href="{% url 'candidate_screening_view' job.slug %}" class="btn btn-main-action">
|
||||||
<i class="fas fa-layer-group me-1"></i> {% trans "Manage Applicants" %}
|
<i class="fas fa-layer-group me-1"></i> {% trans "Manage Applicants" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'request_cvs_download' job.slug %}" class="btn btn-main-action">
|
<a href="{% url 'request_cvs_download' job.slug %}" class="btn btn-main-action">
|
||||||
<i class="fa-solid fa-download me-1"></i> {% trans "Generate All CVs" %}
|
<i class="fa-solid fa-download me-1"></i> {% trans "Generate All CVs" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'download_ready_cvs' job.slug %}" class="btn btn-outline-primary">
|
<a href="{% url 'download_ready_cvs' job.slug %}" class="btn btn-outline-primary">
|
||||||
<i class="fa-solid fa-eye me-1"></i> {% trans "View All CVs" %}
|
<i class="fa-solid fa-eye me-1"></i> {% trans "View All CVs" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -361,7 +361,10 @@
|
|||||||
{% trans "Manage the custom application forms associated with this job posting." %}
|
{% trans "Manage the custom application forms associated with this job posting." %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if not job.form_template %}
|
<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 Job Form" %}
|
||||||
|
</a>
|
||||||
|
{% comment %} {% if not job.form_template %}
|
||||||
<a href="{% url 'create_form_template' %}" class="btn btn-main-action">
|
<a href="{% url 'create_form_template' %}" class="btn btn-main-action">
|
||||||
<i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %}
|
<i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %}
|
||||||
</a>
|
</a>
|
||||||
@ -377,7 +380,7 @@
|
|||||||
<p>{% trans "This job status is not active, the form will appear once the job is made active"%}</p>
|
<p>{% trans "This job status is not active, the form will appear once the job is made active"%}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
--kaauh-danger: #dc3545;
|
--kaauh-danger: #dc3545;
|
||||||
--kaauh-info: #17a2b8;
|
--kaauh-info: #17a2b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #f8f9fa; /* Subtle light background */
|
background-color: #f8f9fa; /* Subtle light background */
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
@ -106,7 +106,7 @@
|
|||||||
.table-hover tbody tr:hover {
|
.table-hover tbody tr:hover {
|
||||||
background-color: #f0f4f7;
|
background-color: #f0f4f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --------------------------------------------------------
|
/* --------------------------------------------------------
|
||||||
* OPTIMIZED MAIN TABLE COLUMN WIDTHS (Total must be 100%)
|
* OPTIMIZED MAIN TABLE COLUMN WIDTHS (Total must be 100%)
|
||||||
* --------------------------------------------------------
|
* --------------------------------------------------------
|
||||||
@ -155,12 +155,12 @@
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Strong visual separator before metrics data */
|
/* Strong visual separator before metrics data */
|
||||||
.table tbody tr td:nth-child(6) {
|
.table tbody tr td:nth-child(6) {
|
||||||
border-left: 2px solid var(--kaauh-teal-dark) !important;
|
border-left: 2px solid var(--kaauh-teal-dark) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subtle vertical lines between metric data cells */
|
/* Subtle vertical lines between metric data cells */
|
||||||
.table tbody td.candidate-data-cell:not(:first-child) {
|
.table tbody td.candidate-data-cell:not(:first-child) {
|
||||||
border-left: 1px solid #f0f4f7;
|
border-left: 1px solid #f0f4f7;
|
||||||
@ -207,7 +207,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
|
|
||||||
{# --- MAIN HEADER AND ACTION BUTTON --- #}
|
{# --- MAIN HEADER AND ACTION BUTTON --- #}
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||||
@ -276,8 +276,9 @@
|
|||||||
<th scope="col" rowspan="2">{% trans "Source" %}</th>
|
<th scope="col" rowspan="2">{% trans "Source" %}</th>
|
||||||
<th scope="col" rowspan="2">{% trans "Max Apps" %}</th>
|
<th scope="col" rowspan="2">{% trans "Max Apps" %}</th>
|
||||||
<th scope="col" rowspan="2">{% trans "Deadline" %}</th>
|
<th scope="col" rowspan="2">{% trans "Deadline" %}</th>
|
||||||
<th scope="col" rowspan="2">{% trans "Submission" %}</th>
|
<th scope="col" rowspan="2">{% trans "Assigned To" %}</th>
|
||||||
|
<th scope="col" rowspan="2"></th>
|
||||||
|
|
||||||
|
|
||||||
<th scope="col" colspan="6" class="candidate-management-header-title">
|
<th scope="col" colspan="6" class="candidate-management-header-title">
|
||||||
{% trans "Applicants Metrics (Current Stage Count)" %}
|
{% trans "Applicants Metrics (Current Stage Count)" %}
|
||||||
@ -306,19 +307,28 @@
|
|||||||
<td>{{ job.get_source }}</td>
|
<td>{{ job.get_source }}</td>
|
||||||
<td>{{ job.max_applications }}</td>
|
<td>{{ job.max_applications }}</td>
|
||||||
<td>{{ job.application_deadline|date:"d-m-Y" }}</td>
|
<td>{{ job.application_deadline|date:"d-m-Y" }}</td>
|
||||||
|
{% if job.assigned_to %}
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-primary-theme">
|
||||||
|
{{ job.assigned_to.first_name|capfirst }} {{ job.assigned_to.last_name|capfirst }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{% trans "Unassigned" %}</td>
|
||||||
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
{% if job.form_template %}
|
{% if job.form_template %}
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<a href="{% url 'form_template_submissions_list' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'All Application Submissions' %}">
|
<a href="{% url 'form_template_submissions_list' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'All Application Submissions' %}">
|
||||||
<i class="fas fa-file-alt text-primary-theme"></i>
|
<i class="fas fa-file-alt text-primary-theme"></i>{% trans "Submissions" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-muted small"></span>
|
<span class="text-muted small"></span>
|
||||||
{% endif%}
|
{% endif%}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
{# CANDIDATE MANAGEMENT DATA #}
|
{# CANDIDATE MANAGEMENT DATA #}
|
||||||
<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-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-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>
|
||||||
@ -366,7 +376,7 @@
|
|||||||
<i class="fas fa-eye me-1"></i> {% trans "View Job Details" %}
|
<i class="fas fa-eye me-1"></i> {% trans "View Job Details" %}
|
||||||
</a>
|
</a>
|
||||||
<div class="btn-group btn-group-sm">
|
<div class="btn-group btn-group-sm">
|
||||||
|
|
||||||
{% if job.form_template %}
|
{% if job.form_template %}
|
||||||
<a href="{% url 'form_template_submissions_list' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Submissions' %}">
|
<a href="{% url 'form_template_submissions_list' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Submissions' %}">
|
||||||
<i class="fas fa-file-alt me-1"></i>{% trans "Submissions" %}
|
<i class="fas fa-file-alt me-1"></i>{% trans "Submissions" %}
|
||||||
@ -382,9 +392,9 @@
|
|||||||
{# --- END CARD VIEW --- #}
|
{# --- END CARD VIEW --- #}
|
||||||
</div>
|
</div>
|
||||||
{# --- END OF JOB LIST CONTAINER --- #}
|
{# --- END OF JOB LIST CONTAINER --- #}
|
||||||
|
|
||||||
{% include "includes/paginator.html" %}
|
{% include "includes/paginator.html" %}
|
||||||
|
|
||||||
{% if not jobs and not job_list_data and not page_obj %}
|
{% if not jobs and not job_list_data and not page_obj %}
|
||||||
<div class="text-center py-5 card shadow-sm mt-4">
|
<div class="text-center py-5 card shadow-sm mt-4">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n crispy_forms_tags %}
|
{% load static i18n crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Create Person - {{ block.super }}{% endblock %}
|
{% block title %}Create Applicant - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
@ -184,9 +184,9 @@
|
|||||||
|
|
||||||
<form method="post" enctype="multipart/form-data" id="person-form">
|
<form method="post" enctype="multipart/form-data" id="person-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{{form|crispy}}
|
||||||
<!-- Profile Image Section -->
|
<!-- Profile Image Section -->
|
||||||
<div class="row mb-4">
|
{% comment %} <div class="row mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="profile-image-upload" onclick="document.getElementById('id_profile_image').click()">
|
<div class="profile-image-upload" onclick="document.getElementById('id_profile_image').click()">
|
||||||
<div id="image-preview-container">
|
<div id="image-preview-container">
|
||||||
@ -261,7 +261,7 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
{{ form.address }}
|
{{ form.address }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> {% endcomment %}
|
||||||
|
|
||||||
<!-- LinkedIn Profile Section -->
|
<!-- LinkedIn Profile Section -->
|
||||||
{% comment %} <div class="row mb-4">
|
{% comment %} <div class="row mb-4">
|
||||||
@ -292,11 +292,8 @@
|
|||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<button type="reset" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-undo me-1"></i> {% trans "Reset" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-main-action">
|
<button type="submit" class="btn btn-main-action">
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Create Person" %}
|
<i class="fas fa-save me-1"></i> {% trans "Create Applicant" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -308,141 +305,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Profile Image Preview
|
|
||||||
const profileImageInput = document.getElementById('id_profile_image');
|
|
||||||
const imagePreviewContainer = document.getElementById('image-preview-container');
|
|
||||||
|
|
||||||
profileImageInput.addEventListener('change', function(e) {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file && file.type.startsWith('image/')) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
imagePreviewContainer.innerHTML = `
|
|
||||||
<img src="${e.target.result}" alt="Profile Preview" class="profile-image-preview">
|
|
||||||
<h5 class="text-muted mt-3">${file.name}</h5>
|
|
||||||
<p class="text-muted small">{% trans "Click to change photo" %}</p>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Form Validation
|
|
||||||
const form = document.getElementById('person-form');
|
|
||||||
form.addEventListener('submit', function(e) {
|
|
||||||
const submitBtn = form.querySelector('button[type="submit"]');
|
|
||||||
submitBtn.classList.add('loading');
|
|
||||||
submitBtn.disabled = true;
|
|
||||||
|
|
||||||
// Basic validation
|
|
||||||
const firstName = document.getElementById('id_first_name').value.trim();
|
|
||||||
const lastName = document.getElementById('id_last_name').value.trim();
|
|
||||||
const email = document.getElementById('id_email').value.trim();
|
|
||||||
|
|
||||||
if (!firstName || !lastName) {
|
|
||||||
e.preventDefault();
|
|
||||||
submitBtn.classList.remove('loading');
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
alert('{% trans "First name and last name are required." %}');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (email && !isValidEmail(email)) {
|
|
||||||
e.preventDefault();
|
|
||||||
submitBtn.classList.remove('loading');
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
alert('{% trans "Please enter a valid email address." %}');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Email validation helper
|
|
||||||
function isValidEmail(email) {
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
return emailRegex.test(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkedIn URL validation
|
|
||||||
const linkedinInput = document.getElementById('id_linkedin_profile');
|
|
||||||
linkedinInput.addEventListener('blur', function() {
|
|
||||||
const value = this.value.trim();
|
|
||||||
if (value && !isValidLinkedInURL(value)) {
|
|
||||||
this.classList.add('is-invalid');
|
|
||||||
if (!this.nextElementSibling || !this.nextElementSibling.classList.contains('invalid-feedback')) {
|
|
||||||
const feedback = document.createElement('div');
|
|
||||||
feedback.className = 'invalid-feedback';
|
|
||||||
feedback.textContent = '{% trans "Please enter a valid LinkedIn URL" %}';
|
|
||||||
this.parentNode.appendChild(feedback);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.classList.remove('is-invalid');
|
|
||||||
const feedback = this.parentNode.querySelector('.invalid-feedback');
|
|
||||||
if (feedback) feedback.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function isValidLinkedInURL(url) {
|
|
||||||
const linkedinRegex = /^https?:\/\/(www\.)?linkedin\.com\/.+/i;
|
|
||||||
return linkedinRegex.test(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drag and Drop functionality
|
|
||||||
const uploadArea = document.querySelector('.profile-image-upload');
|
|
||||||
|
|
||||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
||||||
uploadArea.addEventListener(eventName, preventDefaults, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
function preventDefaults(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
['dragenter', 'dragover'].forEach(eventName => {
|
|
||||||
uploadArea.addEventListener(eventName, highlight, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
['dragleave', 'drop'].forEach(eventName => {
|
|
||||||
uploadArea.addEventListener(eventName, unhighlight, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
function highlight(e) {
|
|
||||||
uploadArea.style.borderColor = 'var(--kaauh-teal)';
|
|
||||||
uploadArea.style.backgroundColor = 'var(--kaauh-gray-light)';
|
|
||||||
}
|
|
||||||
|
|
||||||
function unhighlight(e) {
|
|
||||||
uploadArea.style.borderColor = 'var(--kaauh-border)';
|
|
||||||
uploadArea.style.backgroundColor = 'transparent';
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadArea.addEventListener('drop', handleDrop, false);
|
|
||||||
|
|
||||||
function handleDrop(e) {
|
|
||||||
const dt = e.dataTransfer;
|
|
||||||
const files = dt.files;
|
|
||||||
|
|
||||||
if (files.length > 0) {
|
|
||||||
profileImageInput.files = files;
|
|
||||||
const event = new Event('change', { bubbles: true });
|
|
||||||
profileImageInput.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" />
|
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('.select2').select2();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@ -121,7 +121,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% comment %} <li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown"
|
<a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown"
|
||||||
data-bs-offset="0, 8" aria-expanded="false" aria-label="{% trans 'Toggle language menu' %}">
|
data-bs-offset="0, 8" aria-expanded="false" aria-label="{% trans 'Toggle language menu' %}">
|
||||||
<i class="fas fa-globe me-1"></i>
|
<i class="fas fa-globe me-1"></i>
|
||||||
|
|||||||
@ -17,7 +17,7 @@
|
|||||||
--kaauh-danger: #dc3545;
|
--kaauh-danger: #dc3545;
|
||||||
--kaauh-warning: #ffc107;
|
--kaauh-warning: #ffc107;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: #f8f9fa; /* Light background for better contrast */
|
background-color: #f8f9fa; /* Light background for better contrast */
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@
|
|||||||
border-color: var(--kaauh-teal-dark);
|
border-color: var(--kaauh-teal-dark);
|
||||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Secondary Action Button (Teal Outline) */
|
/* Secondary Action Button (Teal Outline) */
|
||||||
.btn-outline-primary-teal {
|
.btn-outline-primary-teal {
|
||||||
color: var(--kaauh-teal);
|
color: var(--kaauh-teal);
|
||||||
@ -69,7 +69,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--kaauh-primary-text);
|
color: var(--kaauh-primary-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Applying Bootstrap classes to Django fields if not done in the form definition */
|
/* Applying Bootstrap classes to Django fields if not done in the form definition */
|
||||||
.kaauh-field-control > input,
|
.kaauh-field-control > input,
|
||||||
.kaauh-field-control > textarea,
|
.kaauh-field-control > textarea,
|
||||||
@ -104,7 +104,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||||
<i class="fas fa-tasks me-2"></i>
|
<i class="fas fa-tasks me-2"></i>
|
||||||
{{ title }}
|
{% trans "Create New Assignment" %}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">
|
<p class="text-muted mb-0">
|
||||||
{% trans "Assign a job to an external hiring agency" %}
|
{% trans "Assign a job to an external hiring agency" %}
|
||||||
@ -120,22 +120,22 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="row g-3 mb-4">
|
<div class="row g-3 mb-4">
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="{{ form.agency.id_for_label }}" class="form-label">
|
<label for="{{ form.agency.id_for_label }}" class="form-label">
|
||||||
{{ form.agency.label }} <span class="text-danger">*</span>
|
{{ form.agency.label }} <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
{# Wrapper Div for styling consistency (Assumes agency is a SELECT field) #}
|
{# Wrapper Div for styling consistency (Assumes agency is a SELECT field) #}
|
||||||
<div class="kaauh-field-control">
|
<div class="kaauh-field-control">
|
||||||
{{ form.agency|attr:'class:form-select' }}
|
{{ form.agency|attr:'class:form-select' }}
|
||||||
</div>
|
</div>
|
||||||
{% if form.agency.errors %}
|
{% if form.agency.errors %}
|
||||||
<div class="text-danger small mt-1">
|
<div class="text-danger small mt-1">
|
||||||
{% for error in form.agency.errors %}{{ error }}{% endfor %}
|
{% for error in form.agency.errors %}{{ error }}{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<label for="{{ form.job.id_for_label }}" class="form-label">
|
<label for="{{ form.job.id_for_label }}" class="form-label">
|
||||||
{{ form.job.label }} <span class="text-danger">*</span>
|
{{ form.job.label }} <span class="text-danger">*</span>
|
||||||
@ -213,7 +213,7 @@
|
|||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="btn btn-main-action">
|
<button type="submit" class="btn btn-main-action">
|
||||||
<i class="fas fa-save me-1"></i> {{ button_text }}
|
<i class="fas fa-save me-1"></i> {% trans "Create Assignment" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@ -227,10 +227,10 @@
|
|||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// --- Consistency Check: Ensure Django widgets have the Bootstrap classes ---
|
// --- Consistency Check: Ensure Django widgets have the Bootstrap classes ---
|
||||||
// If your form fields are NOT already adding classes via widget attrs in the Django form,
|
// If your form fields are NOT already adding classes via widget attrs in the Django form,
|
||||||
// you MUST add the following utility filter to your project to make this template work:
|
// you MUST add the following utility filter to your project to make this template work:
|
||||||
// `|attr:'class:form-control'`
|
// `|attr:'class:form-control'`
|
||||||
|
|
||||||
// Auto-populate agency field when job is selected
|
// Auto-populate agency field when job is selected
|
||||||
const jobSelect = document.getElementById('{{ form.job.id_for_label }}');
|
const jobSelect = document.getElementById('{{ form.job.id_for_label }}');
|
||||||
const agencySelect = document.getElementById('{{ form.agency.id_for_label }}');
|
const agencySelect = document.getElementById('{{ form.agency.id_for_label }}');
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
--kaauh-success: #28a745;
|
--kaauh-success: #28a745;
|
||||||
--kaauh-danger: #dc3545;
|
--kaauh-danger: #dc3545;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Inter', sans-serif;
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
@ -146,9 +146,9 @@
|
|||||||
/* ------------------------------------------- */
|
/* ------------------------------------------- */
|
||||||
.kaats-spinner {
|
.kaats-spinner {
|
||||||
animation: kaats-spinner-rotate 1.5s linear infinite;
|
animation: kaats-spinner-rotate 1.5s linear infinite;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +160,7 @@
|
|||||||
.kaats-spinner circle {
|
.kaats-spinner circle {
|
||||||
stroke: var(--kaauh-border, #e9ecef);
|
stroke: var(--kaauh-border, #e9ecef);
|
||||||
fill: none;
|
fill: none;
|
||||||
stroke-width: 5;
|
stroke-width: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes kaats-spinner-rotate {
|
@keyframes kaats-spinner-rotate {
|
||||||
@ -225,14 +225,14 @@
|
|||||||
<option value="{{ job.slug }}" {% if job_filter == job.slug %}selected{% endif %}>{{ job.title }}</option>
|
<option value="{{ job.slug }}" {% if job_filter == job.slug %}selected{% endif %}>{{ job.title }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<label for="stage_filter" class="form-label small text-muted">{% trans "Filter by Stages" %}</label>
|
<label for="stage_filter" class="form-label small text-muted">{% trans "Filter by Stages" %}</label>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
|
|
||||||
<select name="stage" id="stage_filter" class="form-select form-select-sm">
|
<select name="stage" id="stage_filter" class="form-select form-select-sm">
|
||||||
<option value="">{% trans "All Stages" %}</option>
|
<option value="">{% trans "All Stages" %}</option>
|
||||||
<option value="Applied" {% if stage_filter == 'Applied' %}selected{% endif %}>{% trans "Applied" %}</option>
|
<option value="Applied" {% if stage_filter == 'Applied' %}selected{% endif %}>{% trans "Applied" %}</option>
|
||||||
@ -279,7 +279,7 @@
|
|||||||
<th scope="col" >{% trans "Major" %}</th>
|
<th scope="col" >{% trans "Major" %}</th>
|
||||||
<th scope="col" >{% trans "Stage" %}</th>
|
<th scope="col" >{% trans "Stage" %}</th>
|
||||||
<th scope="col">{% trans "Hiring Source" %}</th>
|
<th scope="col">{% trans "Hiring Source" %}</th>
|
||||||
<th scope="col" >{% trans "created At" %}</th>
|
<th scope="col" >{% trans "Created At" %}</th>
|
||||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -365,11 +365,11 @@
|
|||||||
<span class="d-block mb-1"><i class="fas fa-envelope"></i> {{ candidate.email }}</span>
|
<span class="d-block mb-1"><i class="fas fa-envelope"></i> {{ candidate.email }}</span>
|
||||||
<span class="d-block mb-1"><i class="fas fa-phone-alt"></i> {{ candidate.phone|default:"N/A" }}</span>
|
<span class="d-block mb-1"><i class="fas fa-phone-alt"></i> {{ candidate.phone|default:"N/A" }}</span>
|
||||||
<span class="d-block mb-1"><i class="fas fa-calendar-alt"></i> {{ candidate.created_at|date:"d-m-Y" }}</span>
|
<span class="d-block mb-1"><i class="fas fa-calendar-alt"></i> {{ candidate.created_at|date:"d-m-Y" }}</span>
|
||||||
<span class="d-block mt-2"><i class="fas fa-briefcase"></i>
|
<span class="d-block mt-2"><i class="fas fa-briefcase"></i>
|
||||||
<span class="badge bg-primary"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-decoration-none text-white">{{ candidate.job.title }}</a></span>
|
<span class="badge bg-primary"><a href="{% url 'job_detail' candidate.job.slug %}" class="text-decoration-none text-white">{{ candidate.job.title }}</a></span>
|
||||||
</span>
|
</span>
|
||||||
{% if candidate.hiring_agency %}
|
{% if candidate.hiring_agency %}
|
||||||
<span class="d-block mt-2"><i class="fas fa-building"></i>
|
<span class="d-block mt-2"><i class="fas fa-building"></i>
|
||||||
<a href="{% url 'agency_detail' candidate.hiring_agency.slug %}" class="text-decoration-none">
|
<a href="{% url 'agency_detail' candidate.hiring_agency.slug %}" class="text-decoration-none">
|
||||||
<span class="badge bg-info">{{ candidate.hiring_agency.name }}</span>
|
<span class="badge bg-info">{{ candidate.hiring_agency.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<style>
|
<style>
|
||||||
/* Custom styles for card and text accent */
|
/* Custom styles for card and text accent */
|
||||||
.form-card {
|
.form-card {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
@ -23,7 +23,7 @@
|
|||||||
.text-accent:hover {
|
.text-accent:hover {
|
||||||
color: #004d55 !important; /* Darker teal hover */
|
color: #004d55 !important; /* Darker teal hover */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Removed aggressive !important button overrides from here */
|
/* Removed aggressive !important button overrides from here */
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@ -34,7 +34,7 @@
|
|||||||
<div class="form-card">
|
<div class="form-card">
|
||||||
|
|
||||||
<h2 id="form-title" class="h3 fw-bold mb-4 text-center">
|
<h2 id="form-title" class="h3 fw-bold mb-4 text-center">
|
||||||
<i class="fas fa-user-plus me-2 text-accent"></i>{% trans "Create Staff User" %}
|
<i class="fas fa-user-plus me-2 text-accent"></i>{% trans "Create User" %}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
@ -56,17 +56,17 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# 🚀 GUARANTEED FIX: Using inline CSS variables to override Bootstrap Primary color #}
|
{# 🚀 GUARANTEED FIX: Using inline CSS variables to override Bootstrap Primary color #}
|
||||||
<button type="submit"
|
<button type="submit"
|
||||||
class="btn btn-primary w-100 mt-3"
|
class="btn btn-primary w-100 mt-3"
|
||||||
style="--bs-btn-bg: #007a88;
|
style="--bs-btn-bg: #007a88;
|
||||||
--bs-btn-border-color: #007a88;
|
--bs-btn-border-color: #007a88;
|
||||||
--bs-btn-hover-bg: #004d55;
|
--bs-btn-hover-bg: #004d55;
|
||||||
--bs-btn-hover-border-color: #004d55;
|
--bs-btn-hover-border-color: #004d55;
|
||||||
--bs-btn-active-bg: #004d55;
|
--bs-btn-active-bg: #004d55;
|
||||||
--bs-btn-active-border-color: #004d55;
|
--bs-btn-active-border-color: #004d55;
|
||||||
--bs-btn-focus-shadow-rgb: 40, 167, 69;
|
--bs-btn-focus-shadow-rgb: 40, 167, 69;
|
||||||
--bs-btn-color: #ffffff;">
|
--bs-btn-color: #ffffff;">
|
||||||
<i class="fas fa-save me-2"></i>{% trans "Create Staff User" %}
|
<i class="fas fa-save me-2"></i>{% trans "Create User" %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user