Merge pull request 'encrpytion removed' (#82) from frontend into main
Reviewed-on: #82
This commit is contained in:
commit
bfea43df8f
@ -62,6 +62,7 @@ INSTALLED_APPS = [
|
||||
"django_q",
|
||||
"widget_tweaks",
|
||||
"easyaudit",
|
||||
"mathfilters"
|
||||
]
|
||||
|
||||
|
||||
@ -491,4 +492,45 @@ MESSAGE_TAGS = {
|
||||
AUTH_USER_MODEL = "recruitment.CustomUser"
|
||||
|
||||
|
||||
ZOOM_WEBHOOK_API_KEY = "2GNDC5Rvyw9AHoGikHXsQB"
|
||||
ZOOM_WEBHOOK_API_KEY = "2GNDC5Rvyw9AHoGikHXsQB"
|
||||
|
||||
|
||||
|
||||
#logger:
|
||||
LOGGING={
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"handlers": {
|
||||
"file": {
|
||||
"class": "logging.FileHandler",
|
||||
"filename": os.path.join(BASE_DIR, "general.log"),
|
||||
"level": "DEBUG",
|
||||
"formatter": "verbose",
|
||||
},
|
||||
"console":{
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "DEBUG",
|
||||
"formatter": "simple"
|
||||
}
|
||||
|
||||
},
|
||||
"loggers": {
|
||||
"": {
|
||||
"handlers": ["file", "console"],
|
||||
"level": "DEBUG",
|
||||
"propagate": True,
|
||||
},
|
||||
},
|
||||
"formatters": {
|
||||
"verbose": {
|
||||
"format": "[{asctime}] {levelname} [{name}:{lineno}] {message}",
|
||||
"style": "{",
|
||||
},
|
||||
"simple": {
|
||||
"format": "{levelname} {message}",
|
||||
"style": "{",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ from .models import (
|
||||
JobPosting, Application, TrainingMaterial,
|
||||
FormTemplate, FormStage, FormField, FormSubmission, FieldResponse,
|
||||
SharedFormTemplate, Source, HiringAgency, IntegrationLog,BulkInterviewTemplate,JobPostingImage,Note,
|
||||
AgencyAccessLink, AgencyJobAssignment,Interview,ScheduledInterview
|
||||
AgencyAccessLink, AgencyJobAssignment,Interview,ScheduledInterview,Person
|
||||
)
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
@ -250,4 +250,5 @@ admin.site.register(ScheduledInterview)
|
||||
|
||||
|
||||
admin.site.register(JobPostingImage)
|
||||
admin.site.register(Person)
|
||||
# admin.site.register(User)
|
||||
|
||||
@ -6,6 +6,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.mixins import AccessMixin
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
|
||||
def job_not_expired(view_func):
|
||||
@wraps(view_func)
|
||||
@ -162,3 +163,12 @@ def staff_or_agency_required(view_func):
|
||||
def staff_or_candidate_required(view_func):
|
||||
"""Decorator to restrict view to staff and candidate users."""
|
||||
return user_type_required(['staff', 'candidate'], login_url='/accounts/login/')(view_func)
|
||||
|
||||
|
||||
def is_superuser(user):
|
||||
|
||||
return user.is_authenticated and user.is_superuser
|
||||
|
||||
|
||||
def superuser_required(view_func):
|
||||
return user_passes_test(is_superuser, login_url='/admin/login/?next=/', redirect_field_name=None)(view_func)
|
||||
@ -28,7 +28,8 @@ from .models import (
|
||||
Message,
|
||||
Person,
|
||||
Document,
|
||||
CustomUser
|
||||
CustomUser,
|
||||
Interview
|
||||
)
|
||||
|
||||
# from django_summernote.widgets import SummernoteWidget
|
||||
@ -270,7 +271,7 @@ class SourceAdvancedForm(forms.ModelForm):
|
||||
class PersonForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Person
|
||||
fields = ["first_name","middle_name", "last_name", "email", "phone","date_of_birth","nationality","gender","address"]
|
||||
fields = ["first_name","middle_name", "last_name", "email", "phone","date_of_birth","gpa","national_id","nationality","gender","address"]
|
||||
widgets = {
|
||||
"first_name": forms.TextInput(attrs={'class': 'form-control'}),
|
||||
"middle_name": forms.TextInput(attrs={'class': 'form-control'}),
|
||||
@ -281,7 +282,45 @@ class PersonForm(forms.ModelForm):
|
||||
"date_of_birth": forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
"nationality": forms.Select(attrs={'class': 'form-control select2'}),
|
||||
"address": forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
"gpa": forms.TextInput(attrs={'class': 'custom-decimal-input'}),
|
||||
"national_id":forms.NumberInput(attrs={'min': 0, 'step': 1}),
|
||||
}
|
||||
def clean_email(self):
|
||||
email = self.cleaned_data.get('email')
|
||||
|
||||
if not email:
|
||||
|
||||
return email
|
||||
|
||||
|
||||
if email:
|
||||
instance = self.instance
|
||||
qs = CustomUser.objects.filter(email=email) | CustomUser.objects.filter(username=email)
|
||||
if not instance.pk: # Creating new instance
|
||||
|
||||
|
||||
if qs.exists():
|
||||
raise ValidationError(_("A user account with this email address already exists. Please use a different email."))
|
||||
|
||||
else: # Editing existing instance
|
||||
# if (
|
||||
# qs
|
||||
# .exclude(pk=instance.user.pk)
|
||||
# .exists()
|
||||
# ):
|
||||
|
||||
# raise ValidationError(_("An user with this email already exists."))
|
||||
pass
|
||||
|
||||
return email.strip()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return email
|
||||
|
||||
class ApplicationForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
@ -790,11 +829,36 @@ class BulkInterviewTemplateForm(forms.ModelForm):
|
||||
self.fields["applications"].queryset = Application.objects.filter(
|
||||
job__slug=slug, stage="Interview"
|
||||
)
|
||||
self.fields["topic"].initial = "Interview for " + str(
|
||||
self.fields["applications"].queryset.first().job.title
|
||||
)
|
||||
self.fields["start_date"].initial = timezone.now().date()
|
||||
working_days_initial = [0, 1, 2, 3, 6] # Monday to Friday
|
||||
self.fields["working_days"].initial = working_days_initial
|
||||
self.fields["start_time"].initial = "08:00"
|
||||
self.fields["end_time"].initial = "14:00"
|
||||
self.fields["interview_duration"].initial = 30
|
||||
self.fields["buffer_time"].initial = 10
|
||||
self.fields["break_start_time"].initial = "11:30"
|
||||
self.fields["break_end_time"].initial = "12:00"
|
||||
self.fields["physical_address"].initial = "Airport Road, King Khalid International Airport, Riyadh 11564, Saudi Arabia"
|
||||
|
||||
def clean_working_days(self):
|
||||
working_days = self.cleaned_data.get("working_days")
|
||||
return [int(day) for day in working_days]
|
||||
|
||||
class InterviewCancelForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Interview
|
||||
fields = ["cancelled_reason","cancelled_at"]
|
||||
widgets = {
|
||||
"cancelled_reason": forms.Textarea(
|
||||
attrs={"class": "form-control", "rows": 3}
|
||||
),
|
||||
"cancelled_at": forms.DateTimeInput(
|
||||
attrs={"class": "form-control", "type": "datetime-local"}
|
||||
),
|
||||
}
|
||||
|
||||
class NoteForm(forms.ModelForm):
|
||||
"""Form for creating and editing meeting comments"""
|
||||
@ -959,7 +1023,7 @@ class HiringAgencyForm(forms.ModelForm):
|
||||
}
|
||||
),
|
||||
"email": forms.EmailInput(
|
||||
attrs={"class": "form-control"}
|
||||
attrs={"class": "form-control","required": True}
|
||||
),
|
||||
"phone": forms.TextInput(
|
||||
attrs={"class": "form-control"}
|
||||
@ -1048,6 +1112,7 @@ class HiringAgencyForm(forms.ModelForm):
|
||||
# instance = self.instance
|
||||
email = email.lower().strip()
|
||||
if not instance.pk: # Creating new instance
|
||||
print("created ....")
|
||||
if HiringAgency.objects.filter(email=email).exists():
|
||||
raise ValidationError("An agency with this email already exists.")
|
||||
else: # Editing existing instance
|
||||
@ -2292,18 +2357,19 @@ class ApplicantSignupForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Person
|
||||
fields = ["first_name","middle_name","last_name", "email","phone","gpa","nationality", "date_of_birth","gender","address"]
|
||||
fields = ["first_name","middle_name","last_name", "email","phone","gpa","nationality","national_id", "date_of_birth","gender","address"]
|
||||
widgets = {
|
||||
'first_name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'middle_name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'last_name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
||||
'phone': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
# 'gpa': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'gpa': forms.TextInput(attrs={'class': 'custom-decimal-input'}),
|
||||
"nationality": forms.Select(attrs={'class': 'form-control select2'}),
|
||||
'date_of_birth': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
'gender': forms.Select(attrs={'class': 'form-control'}),
|
||||
'address': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
'national_id':forms.NumberInput(attrs={'min': 0, 'step': 1}),
|
||||
}
|
||||
|
||||
def clean(self):
|
||||
@ -2878,4 +2944,17 @@ class ScheduledInterviewUpdateStatusForm(forms.Form):
|
||||
)
|
||||
class Meta:
|
||||
model = ScheduledInterview
|
||||
fields = ['status']
|
||||
fields = ['status']
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
# Filter the choices here
|
||||
EXCLUDED_STATUS = ScheduledInterview.InterviewStatus.CANCELLED
|
||||
filtered_choices = [
|
||||
choice for choice in ScheduledInterview.InterviewStatus.choices
|
||||
if choice[0]!= EXCLUDED_STATUS
|
||||
]
|
||||
|
||||
# Apply the filtered list back to the field
|
||||
self.fields['status'].choices = filtered_choices
|
||||
@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-02 14:21
|
||||
# Generated by Django 5.2.7 on 2025-12-08 15:04
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
@ -73,6 +73,8 @@ class Migration(migrations.Migration):
|
||||
('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')),
|
||||
('duration', models.PositiveIntegerField(verbose_name='Duration (minutes)')),
|
||||
('status', models.CharField(choices=[('waiting', 'Waiting'), ('started', 'Started'), ('ended', 'Ended'), ('cancelled', 'Cancelled')], db_index=True, default='waiting', max_length=20)),
|
||||
('cancelled_at', models.DateTimeField(blank=True, null=True, verbose_name='Cancelled At')),
|
||||
('cancelled_reason', models.TextField(blank=True, null=True, verbose_name='Cancellation Reason')),
|
||||
('meeting_id', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='External Meeting ID')),
|
||||
('password', models.CharField(blank=True, max_length=20, null=True)),
|
||||
('zoom_gateway_response', models.JSONField(blank=True, null=True)),
|
||||
@ -150,7 +152,7 @@ class Migration(migrations.Migration):
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('user_type', models.CharField(choices=[('staff', 'Staff'), ('agency', 'Agency'), ('candidate', 'Candidate')], default='staff', max_length=20, verbose_name='User Type')),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Phone')),
|
||||
('phone', models.CharField(blank=True, null=True, verbose_name='Phone')),
|
||||
('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size], verbose_name='Profile Image')),
|
||||
('designation', models.CharField(blank=True, max_length=100, null=True, verbose_name='Designation')),
|
||||
('email', models.EmailField(error_messages={'unique': 'A user with this email already exists.'}, max_length=254, unique=True)),
|
||||
@ -242,8 +244,8 @@ class Migration(migrations.Migration):
|
||||
('slug', django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug')),
|
||||
('name', models.CharField(max_length=200, unique=True, verbose_name='Agency Name')),
|
||||
('contact_person', models.CharField(blank=True, max_length=150, verbose_name='Contact Person')),
|
||||
('email', models.EmailField(blank=True, max_length=254)),
|
||||
('phone', models.CharField(blank=True, max_length=20)),
|
||||
('email', models.EmailField(max_length=254, unique=True)),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True)),
|
||||
('website', models.URLField(blank=True)),
|
||||
('notes', models.TextField(blank=True, help_text='Internal notes about the agency')),
|
||||
('country', django_countries.fields.CountryField(blank=True, max_length=2, null=True)),
|
||||
@ -485,10 +487,11 @@ class Migration(migrations.Migration):
|
||||
('last_name', models.CharField(max_length=255, verbose_name='Last Name')),
|
||||
('middle_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='Middle Name')),
|
||||
('email', models.EmailField(db_index=True, max_length=254, unique=True, verbose_name='Email')),
|
||||
('phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='Phone')),
|
||||
('phone', models.CharField(blank=True, null=True, verbose_name='Phone')),
|
||||
('date_of_birth', models.DateField(blank=True, null=True, verbose_name='Date of Birth')),
|
||||
('gender', models.CharField(blank=True, choices=[('M', 'Male'), ('F', 'Female')], max_length=1, null=True, verbose_name='Gender')),
|
||||
('gpa', models.DecimalField(blank=True, decimal_places=2, max_digits=3, null=True, verbose_name='GPA')),
|
||||
('gpa', models.DecimalField(decimal_places=2, help_text='GPA must be between 0 and 4.', max_digits=3, verbose_name='GPA')),
|
||||
('national_id', models.CharField(help_text='Enter the national id or iqama number')),
|
||||
('nationality', django_countries.fields.CountryField(blank=True, max_length=2, null=True, verbose_name='Nationality')),
|
||||
('address', models.TextField(blank=True, null=True, verbose_name='Address')),
|
||||
('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size], verbose_name='Profile Image')),
|
||||
|
||||
@ -18,10 +18,11 @@ from .validators import validate_hash_tags, validate_image_size
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import F, Value, IntegerField, CharField
|
||||
from django.db.models import F, Value, IntegerField, CharField,Q
|
||||
from django.db.models.functions import Coalesce, Cast
|
||||
from django.db.models.fields.json import KeyTransform, KeyTextTransform
|
||||
|
||||
|
||||
class EmailContent(models.Model):
|
||||
subject = models.CharField(max_length=255, verbose_name=_("Subject"))
|
||||
message = CKEditor5Field(verbose_name=_("Message Body"))
|
||||
@ -47,7 +48,7 @@ class CustomUser(AbstractUser):
|
||||
max_length=20, choices=USER_TYPES, default="staff", verbose_name=_("User Type")
|
||||
)
|
||||
phone = models.CharField(
|
||||
max_length=20, blank=True, null=True, verbose_name=_("Phone")
|
||||
blank=True, null=True, verbose_name=_("Phone")
|
||||
)
|
||||
profile_image = models.ImageField(
|
||||
null=True,
|
||||
@ -65,11 +66,20 @@ class CustomUser(AbstractUser):
|
||||
"unique": _("A user with this email already exists."),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("User")
|
||||
verbose_name_plural = _("Users")
|
||||
|
||||
@property
|
||||
def get_unread_message_count(self):
|
||||
message_list = (
|
||||
Message.objects.filter(Q(recipient=self), is_read=False)
|
||||
)
|
||||
return message_list.count() or 0
|
||||
|
||||
|
||||
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@ -513,7 +523,7 @@ class Person(Base):
|
||||
verbose_name=_("Email"),
|
||||
)
|
||||
phone = models.CharField(
|
||||
max_length=20, blank=True, null=True, verbose_name=_("Phone")
|
||||
blank=True, null=True, verbose_name=_("Phone")
|
||||
)
|
||||
date_of_birth = models.DateField(
|
||||
null=True, blank=True, verbose_name=_("Date of Birth")
|
||||
@ -526,8 +536,11 @@ class Person(Base):
|
||||
verbose_name=_("Gender"),
|
||||
)
|
||||
gpa = models.DecimalField(
|
||||
max_digits=3, decimal_places=2, blank=True, null=True, verbose_name=_("GPA")
|
||||
max_digits=3, decimal_places=2, verbose_name=_("GPA"),help_text=_("GPA must be between 0 and 4.")
|
||||
)
|
||||
national_id = models.CharField(
|
||||
help_text=_("Enter the national id or iqama number")
|
||||
)
|
||||
nationality = CountryField(blank=True, null=True, verbose_name=_("Nationality"))
|
||||
address = models.TextField(blank=True, null=True, verbose_name=_("Address"))
|
||||
|
||||
@ -1327,6 +1340,8 @@ class Interview(Base):
|
||||
default=Status.WAITING,
|
||||
db_index=True
|
||||
)
|
||||
cancelled_at = models.DateTimeField(null=True, blank=True, verbose_name=_("Cancelled At"))
|
||||
cancelled_reason = models.TextField(blank=True, null=True, verbose_name=_("Cancellation Reason"))
|
||||
|
||||
# Remote-specific (nullable)
|
||||
meeting_id = models.CharField(
|
||||
@ -2077,8 +2092,8 @@ class HiringAgency(Base):
|
||||
contact_person = models.CharField(
|
||||
max_length=150, blank=True, verbose_name=_("Contact Person")
|
||||
)
|
||||
email = models.EmailField(blank=True)
|
||||
phone = models.CharField(max_length=20, blank=True)
|
||||
email = models.EmailField(unique=True)
|
||||
phone = models.CharField(max_length=20, blank=True,null=True)
|
||||
website = models.URLField(blank=True)
|
||||
notes = models.TextField(blank=True, help_text=_("Internal notes about the agency"))
|
||||
country = CountryField(blank=True, null=True, blank_label=_("Select country"))
|
||||
@ -2108,6 +2123,7 @@ class HiringAgency(Base):
|
||||
|
||||
# 2. Call the original delete method for the Agency instance
|
||||
super().delete(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
class AgencyJobAssignment(Base):
|
||||
@ -2267,6 +2283,15 @@ class AgencyJobAssignment(Base):
|
||||
# self.save(update_fields=['status'])
|
||||
return True
|
||||
return False
|
||||
@property
|
||||
def applications_submited_count(self):
|
||||
"""Return the number of applications submitted by the agency for this job"""
|
||||
return Application.objects.filter(
|
||||
hiring_agency=self.agency,
|
||||
job=self.job
|
||||
).count()
|
||||
|
||||
|
||||
|
||||
def extend_deadline(self, new_deadline):
|
||||
"""Extend the deadline for this assignment"""
|
||||
@ -2478,7 +2503,7 @@ class Participants(Base):
|
||||
name = models.CharField(
|
||||
max_length=255, verbose_name=_("Participant Name"), null=True, blank=True
|
||||
)
|
||||
email = models.EmailField(verbose_name=_("Email"))
|
||||
email =models.EmailField(verbose_name=_("Email"))
|
||||
phone = models.CharField(
|
||||
max_length=12, verbose_name=_("Phone Number"), null=True, blank=True
|
||||
)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import logging
|
||||
import random
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from django.db import transaction
|
||||
from django_q.models import Schedule
|
||||
@ -437,12 +437,8 @@ def notification_created(sender, instance, created, **kwargs):
|
||||
logger.info(f"Notification cached for SSE: {notification_data}")
|
||||
|
||||
|
||||
def generate_random_password():
|
||||
import string
|
||||
|
||||
return "".join(random.choices(string.ascii_letters + string.digits, k=12))
|
||||
|
||||
|
||||
from .utils import generate_random_password
|
||||
@receiver(post_save, sender=HiringAgency)
|
||||
def hiring_agency_created(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
@ -463,16 +459,19 @@ def hiring_agency_created(sender, instance, created, **kwargs):
|
||||
def person_created(sender, instance, created, **kwargs):
|
||||
if created and not instance.user:
|
||||
logger.info(f"New Person created: {instance.pk} - {instance.email}")
|
||||
user = User.objects.create_user(
|
||||
username=instance.email,
|
||||
first_name=instance.first_name,
|
||||
last_name=instance.last_name,
|
||||
email=instance.email,
|
||||
phone=instance.phone,
|
||||
user_type="candidate",
|
||||
)
|
||||
instance.user = user
|
||||
instance.save()
|
||||
try:
|
||||
user = User.objects.create_user(
|
||||
username=instance.email,
|
||||
first_name=instance.first_name,
|
||||
last_name=instance.last_name,
|
||||
email=instance.email,
|
||||
phone=instance.phone,
|
||||
user_type="candidate",
|
||||
)
|
||||
instance.user = user
|
||||
instance.save()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Source)
|
||||
|
||||
@ -283,7 +283,8 @@ urlpatterns = [
|
||||
name="user_profile_image_update",
|
||||
),
|
||||
path("easy_logs/", views.easy_logs, name="easy_logs"),
|
||||
path("settings/", views.admin_settings, name="admin_settings"),
|
||||
path('settings/',views.settings,name="settings"),
|
||||
path("settings/admin/", views.admin_settings, name="admin_settings"),
|
||||
path("staff/create", views.create_staff_user, name="create_staff_user"),
|
||||
path(
|
||||
"set_staff_password/<int:pk>/",
|
||||
@ -353,6 +354,8 @@ urlpatterns = [
|
||||
# ),
|
||||
# Hiring Agency URLs
|
||||
path("agencies/", views.agency_list, name="agency_list"),
|
||||
path("regenerate_agency_password/<slug:slug>/", views.regenerate_agency_password, name="regenerate_agency_password"),
|
||||
path("deactivate_agency/<slug:slug>/", views.deactivate_agency, name="deactivate_agency"),
|
||||
path("agencies/create/", views.agency_create, name="agency_create"),
|
||||
path("agencies/<slug:slug>/", views.agency_detail, name="agency_detail"),
|
||||
path("agencies/<slug:slug>/update/", views.agency_update, name="agency_update"),
|
||||
|
||||
@ -9,6 +9,7 @@ from django.utils import timezone
|
||||
from .models import ScheduledInterview
|
||||
from django.template.loader import render_to_string
|
||||
from django.core.mail import send_mail
|
||||
import random
|
||||
# nlp = spacy.load("en_core_web_sm")
|
||||
|
||||
# def extract_text_from_pdf(pdf_path):
|
||||
@ -617,3 +618,8 @@ def update_meeting(instance, updated_data):
|
||||
|
||||
|
||||
|
||||
|
||||
def generate_random_password():
|
||||
import string
|
||||
|
||||
return "".join(random.choices(string.ascii_letters + string.digits, k=12))
|
||||
File diff suppressed because it is too large
Load Diff
@ -228,6 +228,8 @@ class ApplicationDeleteView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessa
|
||||
slug_url_kwarg = 'slug'
|
||||
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def retry_scoring_view(request,slug):
|
||||
from django_q.tasks import async_task
|
||||
|
||||
@ -302,6 +304,10 @@ def application_update_stage(request, slug):
|
||||
application.save(update_fields=['stage'])
|
||||
messages.success(request,_("application Stage Updated"))
|
||||
return redirect("application_detail",slug=application.slug)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class TrainingListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
|
||||
model = models.TrainingMaterial
|
||||
@ -755,7 +761,7 @@ STAGE_CONFIG = {
|
||||
'headers': ['Name', 'Email', 'Phone', 'Application Date', 'Offer Status', 'Offer Date', 'Match Score', 'Years Experience', 'Professional Category']
|
||||
},
|
||||
'hired': {
|
||||
'filter': {'offer_status': 'Accepted'},
|
||||
'filter': {'stage': 'Hired'},
|
||||
'fields': ['name', 'email', 'phone', 'created_at', 'offer_date', 'ai_score', 'years_experience', 'professional_category', 'join_date'],
|
||||
'headers': ['Name', 'Email', 'Phone', 'Application Date', 'Hire Date', 'Match Score', 'Years Experience', 'Professional Category', 'Join Date']
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import secrets
|
||||
import string
|
||||
from .models import Source, IntegrationLog
|
||||
from .forms import SourceForm, generate_api_key, generate_api_secret
|
||||
from .decorators import login_required, staff_user_required
|
||||
|
||||
class SourceListView(LoginRequiredMixin, UserPassesTestMixin, ListView):
|
||||
"""List all sources"""
|
||||
@ -182,6 +183,8 @@ class SourceDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
|
||||
messages.success(request, f'Source "{self.object.name}" deleted successfully!')
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def generate_api_keys_view(request, pk):
|
||||
"""Generate new API keys for a specific source"""
|
||||
if not request.user.is_staff:
|
||||
@ -228,6 +231,8 @@ def generate_api_keys_view(request, pk):
|
||||
|
||||
return JsonResponse({'error': 'Invalid request method'}, status=405)
|
||||
|
||||
@login_required
|
||||
@staff_user_required
|
||||
def toggle_source_status_view(request, pk):
|
||||
"""Toggle the active status of a source"""
|
||||
if not request.user.is_staff:
|
||||
@ -267,7 +272,7 @@ def toggle_source_status_view(request, pk):
|
||||
# 'is_active': source.is_active,
|
||||
# 'message': f'Source "{source.name}" {status_text} successfully'
|
||||
# })
|
||||
|
||||
@login_required
|
||||
def copy_to_clipboard_view(request):
|
||||
"""HTMX endpoint to copy text to clipboard"""
|
||||
if request.method == 'POST':
|
||||
|
||||
209
requirements.tx
Normal file
209
requirements.tx
Normal file
@ -0,0 +1,209 @@
|
||||
amqp==5.3.1
|
||||
annotated-types==0.7.0
|
||||
appdirs==1.4.4
|
||||
arrow==1.3.0
|
||||
asgiref==3.10.0
|
||||
asteval==1.0.6
|
||||
astunparse==1.6.3
|
||||
attrs==25.3.0
|
||||
billiard==4.2.2
|
||||
bleach==6.2.0
|
||||
blessed==1.22.0
|
||||
blinker==1.9.0
|
||||
blis==1.3.0
|
||||
boto3==1.40.45
|
||||
botocore==1.40.45
|
||||
bw-migrations==0.2
|
||||
bw2data==4.5
|
||||
bw2parameters==1.1.0
|
||||
bw_processing==1.0
|
||||
cached-property==2.0.1
|
||||
catalogue==2.0.10
|
||||
celery==5.5.3
|
||||
certifi==2025.10.5
|
||||
cffi==2.0.0
|
||||
channels==4.3.1
|
||||
chardet==5.2.0
|
||||
charset-normalizer==3.4.3
|
||||
click==8.3.0
|
||||
click-didyoumean==0.3.1
|
||||
click-plugins==1.1.1.2
|
||||
click-repl==0.3.0
|
||||
cloudpathlib==0.22.0
|
||||
confection==0.1.5
|
||||
constructive_geometries==1.0
|
||||
country_converter==1.3.1
|
||||
crispy-bootstrap5==2025.6
|
||||
cryptography==46.0.2
|
||||
cymem==2.0.11
|
||||
dataflows-tabulator==1.54.3
|
||||
datapackage==1.15.4
|
||||
datastar-py==0.6.5
|
||||
deepdiff==7.0.1
|
||||
Deprecated==1.2.18
|
||||
Django==5.2.7
|
||||
django-allauth==65.12.1
|
||||
django-ckeditor-5==0.2.18
|
||||
django-cors-headers==4.9.0
|
||||
django-countries==7.6.1
|
||||
django-crispy-forms==2.4
|
||||
django-easy-audit==1.3.7
|
||||
django-encrypted-model-fields==0.6.5
|
||||
django-extensions==4.1
|
||||
django-filter==25.1
|
||||
django-js-asset==3.1.2
|
||||
django-picklefield==3.3
|
||||
django-q2==1.8.0
|
||||
django-template-partials==25.2
|
||||
django-unfold==0.67.0
|
||||
django-widget-tweaks==1.5.0
|
||||
django_celery_results==2.6.0
|
||||
djangorestframework==3.16.1
|
||||
docopt==0.6.2
|
||||
dotenv==0.9.9
|
||||
en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl#sha256=1932429db727d4bff3deed6b34cfc05df17794f4a52eeb26cf8928f7c1a0fb85
|
||||
et_xmlfile==2.0.0
|
||||
Faker==37.8.0
|
||||
filelock==3.19.1
|
||||
flexcache==0.3
|
||||
flexparser==0.4
|
||||
fsspec==2025.9.0
|
||||
greenlet==3.2.4
|
||||
hf-xet==1.1.10
|
||||
huggingface-hub==0.35.3
|
||||
idna==3.10
|
||||
ijson==3.4.0
|
||||
isodate==0.7.2
|
||||
Jinja2==3.1.6
|
||||
jmespath==1.0.1
|
||||
joblib==1.5.2
|
||||
jsonlines==4.0.0
|
||||
jsonpointer==3.0.0
|
||||
jsonschema==4.25.1
|
||||
jsonschema-specifications==2025.9.1
|
||||
kombu==5.5.4
|
||||
langcodes==3.5.0
|
||||
language_data==1.3.0
|
||||
linear-tsv==1.1.0
|
||||
llvmlite==0.45.1
|
||||
loguru==0.7.3
|
||||
lxml==6.0.2
|
||||
marisa-trie==1.3.1
|
||||
markdown-it-py==4.0.0
|
||||
MarkupSafe==3.0.3
|
||||
matrix_utils==0.6.2
|
||||
mdurl==0.1.2
|
||||
morefs==0.2.2
|
||||
mpmath==1.3.0
|
||||
mrio-common-metadata==0.2.1
|
||||
murmurhash==1.0.13
|
||||
networkx==3.5
|
||||
numba==0.62.1
|
||||
numpy==2.3.3
|
||||
nvidia-cublas-cu12==12.8.4.1
|
||||
nvidia-cuda-cupti-cu12==12.8.90
|
||||
nvidia-cuda-nvrtc-cu12==12.8.93
|
||||
nvidia-cuda-runtime-cu12==12.8.90
|
||||
nvidia-cudnn-cu12==9.10.2.21
|
||||
nvidia-cufft-cu12==11.3.3.83
|
||||
nvidia-cufile-cu12==1.13.1.3
|
||||
nvidia-curand-cu12==10.3.9.90
|
||||
nvidia-cusolver-cu12==11.7.3.90
|
||||
nvidia-cusparse-cu12==12.5.8.93
|
||||
nvidia-cusparselt-cu12==0.7.1
|
||||
nvidia-nccl-cu12==2.27.3
|
||||
nvidia-nvjitlink-cu12==12.8.93
|
||||
nvidia-nvtx-cu12==12.8.90
|
||||
openpyxl==3.1.5
|
||||
ordered-set==4.1.0
|
||||
packaging==25.0
|
||||
pandas==2.3.3
|
||||
pdfminer.six==20250506
|
||||
pdfplumber==0.11.7
|
||||
peewee==3.18.2
|
||||
pillow==11.3.0
|
||||
Pint==0.25
|
||||
platformdirs==4.4.0
|
||||
preshed==3.0.10
|
||||
prettytable==3.16.0
|
||||
prompt_toolkit==3.0.52
|
||||
psycopg==3.2.11
|
||||
pycparser==2.23
|
||||
pydantic==2.11.10
|
||||
pydantic-settings==2.11.0
|
||||
pydantic_core==2.33.2
|
||||
pyecospold==4.0.0
|
||||
Pygments==2.19.2
|
||||
PyJWT==2.10.1
|
||||
PyMuPDF==1.26.4
|
||||
pyparsing==3.2.5
|
||||
PyPDF2==3.0.1
|
||||
pypdfium2==4.30.0
|
||||
PyPrind==2.11.3
|
||||
pytesseract==0.3.13
|
||||
python-dateutil==2.9.0.post0
|
||||
python-docx==1.2.0
|
||||
python-dotenv==1.1.1
|
||||
python-json-logger==3.3.0
|
||||
pytz==2025.2
|
||||
pyxlsb==1.0.10
|
||||
PyYAML==6.0.3
|
||||
randonneur==0.6.2
|
||||
randonneur_data==0.6.1
|
||||
RapidFuzz==3.14.1
|
||||
rdflib==7.2.1
|
||||
redis==3.5.3
|
||||
referencing==0.36.2
|
||||
regex==2025.9.18
|
||||
requests==2.32.5
|
||||
rfc3986==2.0.0
|
||||
rich==14.1.0
|
||||
rpds-py==0.27.1
|
||||
s3transfer==0.14.0
|
||||
safetensors==0.6.2
|
||||
scikit-learn==1.7.2
|
||||
scipy==1.16.2
|
||||
sentence-transformers==5.1.1
|
||||
setuptools==80.9.0
|
||||
shellingham==1.5.4
|
||||
six==1.17.0
|
||||
smart_open==7.3.1
|
||||
snowflake-id==1.0.2
|
||||
spacy==3.8.7
|
||||
spacy-legacy==3.0.12
|
||||
spacy-loggers==1.0.5
|
||||
SPARQLWrapper==2.0.0
|
||||
sparse==0.17.0
|
||||
SQLAlchemy==2.0.43
|
||||
sqlparse==0.5.3
|
||||
srsly==2.5.1
|
||||
stats_arrays==0.7
|
||||
structlog==25.4.0
|
||||
sympy==1.14.0
|
||||
tableschema==1.21.0
|
||||
thinc==8.3.6
|
||||
threadpoolctl==3.6.0
|
||||
tokenizers==0.22.1
|
||||
toolz==1.0.0
|
||||
torch==2.8.0
|
||||
tqdm==4.67.1
|
||||
transformers==4.57.0
|
||||
triton==3.4.0
|
||||
typer==0.19.2
|
||||
types-python-dateutil==2.9.0.20251008
|
||||
typing-inspection==0.4.2
|
||||
typing_extensions==4.15.0
|
||||
tzdata==2025.2
|
||||
unicodecsv==0.14.1
|
||||
urllib3==2.5.0
|
||||
vine==5.1.0
|
||||
voluptuous==0.15.2
|
||||
wasabi==1.1.3
|
||||
wcwidth==0.2.14
|
||||
weasel==0.4.1
|
||||
webencodings==0.5.1
|
||||
wheel==0.45.1
|
||||
wrapt==1.17.3
|
||||
wurst==0.4
|
||||
xlrd==2.0.2
|
||||
xlsxwriter==3.2.9
|
||||
101
requirements.txt
101
requirements.txt
@ -1,10 +1,8 @@
|
||||
amqp==5.3.1
|
||||
annotated-types==0.7.0
|
||||
anthropic==0.63.0
|
||||
anyio==4.11.0
|
||||
appdirs==1.4.4
|
||||
arrow==1.3.0
|
||||
asgiref==3.9.2
|
||||
asgiref==3.10.0
|
||||
asteval==1.0.6
|
||||
astunparse==1.6.3
|
||||
attrs==25.3.0
|
||||
@ -13,8 +11,8 @@ bleach==6.2.0
|
||||
blessed==1.22.0
|
||||
blinker==1.9.0
|
||||
blis==1.3.0
|
||||
boto3==1.40.37
|
||||
botocore==1.40.37
|
||||
boto3==1.40.45
|
||||
botocore==1.40.45
|
||||
bw-migrations==0.2
|
||||
bw2data==4.5
|
||||
bw2parameters==1.1.0
|
||||
@ -22,7 +20,8 @@ bw_processing==1.0
|
||||
cached-property==2.0.1
|
||||
catalogue==2.0.10
|
||||
celery==5.5.3
|
||||
certifi==2025.8.3
|
||||
certifi==2025.10.5
|
||||
cffi==2.0.0
|
||||
channels==4.3.1
|
||||
chardet==5.2.0
|
||||
charset-normalizer==3.4.3
|
||||
@ -35,50 +34,48 @@ confection==0.1.5
|
||||
constructive_geometries==1.0
|
||||
country_converter==1.3.1
|
||||
crispy-bootstrap5==2025.6
|
||||
cryptography==46.0.2
|
||||
cymem==2.0.11
|
||||
dataflows-tabulator==1.54.3
|
||||
datapackage==1.15.4
|
||||
datastar-py==0.6.5
|
||||
deepdiff==7.0.1
|
||||
Deprecated==1.2.18
|
||||
distro==1.9.0
|
||||
Django==5.2.6
|
||||
django-allauth==65.11.2
|
||||
Django==5.2.7
|
||||
django-allauth==65.12.1
|
||||
django-ckeditor-5==0.2.18
|
||||
django-cors-headers==4.9.0
|
||||
django-countries==7.6.1
|
||||
django-crispy-forms==2.4
|
||||
django-easy-audit==1.3.7
|
||||
django-easy-audit==1.3.7s
|
||||
django-extensions==4.1
|
||||
django-filter==25.1
|
||||
django-js-asset==3.1.2
|
||||
django-picklefield==3.3
|
||||
django-q2==1.8.0
|
||||
django-summernote==0.8.20.0
|
||||
django-template-partials==25.2
|
||||
django-unfold==0.66.0
|
||||
django-unfold==0.67.0
|
||||
django-widget-tweaks==1.5.0
|
||||
django_celery_results==2.6.0
|
||||
djangorestframework==3.16.1
|
||||
docopt==0.6.2
|
||||
dotenv==0.9.9
|
||||
en_core_web_sm @ https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl#sha256=1932429db727d4bff3deed6b34cfc05df17794f4a52eeb26cf8928f7c1a0fb85
|
||||
et_xmlfile==2.0.0
|
||||
Faker==37.8.0
|
||||
filelock==3.19.1
|
||||
flexcache==0.3
|
||||
flexparser==0.4
|
||||
fsspec==2025.9.0
|
||||
gpt-po-translator==1.3.2
|
||||
greenlet==3.2.4
|
||||
h11==0.16.0
|
||||
httpcore==1.0.9
|
||||
httpx==0.28.1
|
||||
hf-xet==1.1.10
|
||||
huggingface-hub==0.35.3
|
||||
idna==3.10
|
||||
ijson==3.4.0
|
||||
iniconfig==2.1.0
|
||||
isodate==0.7.2
|
||||
isort==5.13.2
|
||||
Jinja2==3.1.6
|
||||
jiter==0.11.1
|
||||
jmespath==1.0.1
|
||||
joblib==1.5.2
|
||||
jsonlines==4.0.0
|
||||
jsonpointer==3.0.0
|
||||
jsonschema==4.25.1
|
||||
@ -87,37 +84,52 @@ kombu==5.5.4
|
||||
langcodes==3.5.0
|
||||
language_data==1.3.0
|
||||
linear-tsv==1.1.0
|
||||
llvmlite==0.45.0
|
||||
llvmlite==0.45.1
|
||||
loguru==0.7.3
|
||||
lxml==6.0.2
|
||||
marisa-trie==1.3.1
|
||||
markdown-it-py==4.0.0
|
||||
MarkupSafe==3.0.2
|
||||
MarkupSafe==3.0.3
|
||||
matrix_utils==0.6.2
|
||||
mdurl==0.1.2
|
||||
morefs==0.2.2
|
||||
mpmath==1.3.0
|
||||
mrio-common-metadata==0.2.1
|
||||
murmurhash==1.0.13
|
||||
numba==0.62.0
|
||||
networkx==3.5
|
||||
numba==0.62.1
|
||||
numpy==2.3.3
|
||||
openai==1.99.9
|
||||
nvidia-cublas-cu12==12.8.4.1
|
||||
nvidia-cuda-cupti-cu12==12.8.90
|
||||
nvidia-cuda-nvrtc-cu12==12.8.93
|
||||
nvidia-cuda-runtime-cu12==12.8.90
|
||||
nvidia-cudnn-cu12==9.10.2.21
|
||||
nvidia-cufft-cu12==11.3.3.83
|
||||
nvidia-cufile-cu12==1.13.1.3
|
||||
nvidia-curand-cu12==10.3.9.90
|
||||
nvidia-cusolver-cu12==11.7.3.90
|
||||
nvidia-cusparse-cu12==12.5.8.93
|
||||
nvidia-cusparselt-cu12==0.7.1
|
||||
nvidia-nccl-cu12==2.27.3
|
||||
nvidia-nvjitlink-cu12==12.8.93
|
||||
nvidia-nvtx-cu12==12.8.90
|
||||
openpyxl==3.1.5
|
||||
ordered-set==4.1.0
|
||||
packaging==25.0
|
||||
pandas==2.3.2
|
||||
pandas==2.3.3
|
||||
pdfminer.six==20250506
|
||||
pdfplumber==0.11.7
|
||||
peewee==3.18.2
|
||||
pillow==11.3.0
|
||||
Pint==0.25
|
||||
platformdirs==4.4.0
|
||||
pluggy==1.6.0
|
||||
polib==1.2.0
|
||||
preshed==3.0.10
|
||||
prettytable==3.16.0
|
||||
prompt_toolkit==3.0.52
|
||||
psycopg2-binary==2.9.11
|
||||
pycountry==24.6.1
|
||||
pydantic==2.11.9
|
||||
pydantic-settings==2.10.1
|
||||
psycopg==3.2.11
|
||||
pycparser==2.23
|
||||
pydantic==2.11.10
|
||||
pydantic-settings==2.11.0
|
||||
pydantic_core==2.33.2
|
||||
pyecospold==4.0.0
|
||||
Pygments==2.19.2
|
||||
@ -125,35 +137,36 @@ PyJWT==2.10.1
|
||||
PyMuPDF==1.26.4
|
||||
pyparsing==3.2.5
|
||||
PyPDF2==3.0.1
|
||||
pypdfium2==4.30.0
|
||||
PyPrind==2.11.3
|
||||
pytest==8.3.4
|
||||
pytest-django==4.11.1
|
||||
pytesseract==0.3.13
|
||||
python-dateutil==2.9.0.post0
|
||||
python-docx==1.2.0
|
||||
python-dotenv==1.0.1
|
||||
python-dotenv==1.1.1
|
||||
python-json-logger==3.3.0
|
||||
pytz==2025.2
|
||||
pyxlsb==1.0.10
|
||||
PyYAML==6.0.2
|
||||
PyYAML==6.0.3
|
||||
randonneur==0.6.2
|
||||
randonneur_data==0.6
|
||||
randonneur_data==0.6.1
|
||||
RapidFuzz==3.14.1
|
||||
rdflib==7.2.1
|
||||
redis==3.5.3
|
||||
referencing==0.36.2
|
||||
requests==2.32.3
|
||||
responses==0.25.8
|
||||
regex==2025.9.18
|
||||
requests==2.32.5
|
||||
rfc3986==2.0.0
|
||||
rich==14.1.0
|
||||
rpds-py==0.27.1
|
||||
s3transfer==0.14.0
|
||||
safetensors==0.6.2
|
||||
scikit-learn==1.7.2
|
||||
scipy==1.16.2
|
||||
sentence-transformers==5.1.1
|
||||
setuptools==80.9.0
|
||||
setuptools-scm==8.1.0
|
||||
shellingham==1.5.4
|
||||
six==1.17.0
|
||||
smart_open==7.3.1
|
||||
sniffio==1.3.1
|
||||
snowflake-id==1.0.2
|
||||
spacy==3.8.7
|
||||
spacy-legacy==3.0.12
|
||||
@ -165,15 +178,19 @@ sqlparse==0.5.3
|
||||
srsly==2.5.1
|
||||
stats_arrays==0.7
|
||||
structlog==25.4.0
|
||||
sympy==1.14.0
|
||||
tableschema==1.21.0
|
||||
tenacity==9.0.0
|
||||
thinc==8.3.6
|
||||
tomli==2.2.1
|
||||
threadpoolctl==3.6.0
|
||||
tokenizers==0.22.1
|
||||
toolz==1.0.0
|
||||
torch==2.8.0
|
||||
tqdm==4.67.1
|
||||
transformers==4.57.0
|
||||
triton==3.4.0
|
||||
typer==0.19.2
|
||||
types-python-dateutil==2.9.0.20251008
|
||||
typing-inspection==0.4.1
|
||||
typing-inspection==0.4.2
|
||||
typing_extensions==4.15.0
|
||||
tzdata==2025.2
|
||||
unicodecsv==0.14.1
|
||||
|
||||
@ -632,11 +632,15 @@
|
||||
}
|
||||
break;
|
||||
case 'date':
|
||||
if (value && !/^\d{4}-(0[1-9]|1[0-2])$/.test(value)) {
|
||||
state.fieldErrors[field.id] = 'Please select a valid date';
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
// Regex for YYYY-MM-DD (ISO standard for <input type="date"> output)
|
||||
const yyyyMmDdRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;
|
||||
|
||||
if (value && !yyyyMmDdRegex.test(value)) {
|
||||
// You might want to update the error message based on the input type
|
||||
state.fieldErrors[field.id] = 'Please enter a valid date (e.g., YYYY-MM-DD).';
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -1024,7 +1028,7 @@
|
||||
}
|
||||
else if (field.type === 'date') {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'month';
|
||||
input.type = 'date';
|
||||
input.className = 'form-input';
|
||||
input.placeholder = field.placeholder || 'Select date';
|
||||
input.id = `field_${field.id}`;
|
||||
@ -1265,5 +1269,8 @@
|
||||
|
||||
// Start the application
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
{% endblock content %}
|
||||
@ -20,6 +20,7 @@
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" integrity="sha512-Kc323vGBEqzTmouAECnVceyQqyqdsSiqLQISBL29aUW4U/M7pSPA/gEUZQqv1cwx4OnYxTxve5UMg5GT6L4JJg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<link rel="stylesheet" href="{% static 'css/main.css' %}">
|
||||
<script src="{% static 'js/typo.js' %}"></script>
|
||||
|
||||
|
||||
{% block customCSS %}{% endblock %}
|
||||
@ -115,6 +116,7 @@
|
||||
<i class="fas fa-envelope"></i>
|
||||
</a>
|
||||
</li> {% endcomment %}
|
||||
|
||||
<li class="nav-item me-2 d-none d-lg-block">
|
||||
{% if LANGUAGE_CODE == 'en' %}
|
||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">
|
||||
@ -134,10 +136,14 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="nav-item me-2 d-none d-lg-block">
|
||||
<a class="nav-link text-white" href="{% url 'message_list' %}">
|
||||
<i class="fas fa-envelope"></i> <span>{% trans "Messages" %}</span>
|
||||
</a>
|
||||
|
||||
<li class="nav-item mx-3 d-none d-lg-block mt-2">
|
||||
<a href="{% url 'message_list' %}" class="btn btn-sm btn-outline-warning position-relative">
|
||||
<i class="fas fa-envelope me-1"></i>
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||
{{ request.user.get_unread_message_count }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item dropdown">
|
||||
@ -148,7 +154,7 @@
|
||||
aria-expanded="false"
|
||||
aria-label="{% trans 'Toggle user menu' %}"
|
||||
data-bs-auto-close="outside"
|
||||
data-bs-offset="0, 16" {# Vertical offset remains 16px to prevent clipping #}
|
||||
data-bs-offset="0, 16"
|
||||
>
|
||||
{% if user.profile_image %}
|
||||
<img src="{{ user.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar"
|
||||
@ -156,16 +162,17 @@
|
||||
title="{% trans 'Your account' %}">
|
||||
{% else %}
|
||||
<div class="profile-avatar" title="{% trans 'Your account' %}">
|
||||
{{ user.first_name }} {{ user.last_name }}
|
||||
{% if user.first_name %}
|
||||
{{ user.first_name|first|capfirst }} {{ user.last_name|first|capfirst }}
|
||||
{% else %}
|
||||
{{user.username|first|capfirst}}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</button>
|
||||
|
||||
<ul
|
||||
class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3"
|
||||
style="min-width: 240px;"
|
||||
>
|
||||
<li class="px-4 py-3 ">
|
||||
<ul class="dropdown-menu dropdown-menu-end py-0 shadow border-0 rounded-3" style="min-width: 240px;">
|
||||
<li class="px-4 py-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="me-3 d-flex align-items-center justify-content-center" style="min-width: 48px;">
|
||||
{% if user.profile_image %}
|
||||
@ -185,67 +192,62 @@
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><hr class="dropdown-divider my-1"></li>
|
||||
<div>
|
||||
<li class="nav-item me-3 dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal d-lg-none">
|
||||
{% if LANGUAGE_CODE == 'en' %}
|
||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
<button name="language" value="ar" class="btn bg-primary-theme text-white" type="submit">
|
||||
<span class="me-2">🇸🇦</span> العربية
|
||||
</button>
|
||||
</form>
|
||||
{% elif LANGUAGE_CODE == 'ar' %}
|
||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
<button name="language" value="en" class="btn bg-primary-theme text-white" type="submit">
|
||||
<span class="me-2">🇺🇸</span> English
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
<li class="d-lg-none"><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'message_list' %}"> <i class="fas fa-envelope fs-5 me-3"></i> <span>{% trans "Messages" %}</span></a></li>
|
||||
{% if request.user.is_authenticated %}
|
||||
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'user_detail' request.user.pk %}"><i class="fas fa-user-circle me-3 fs-5"></i> <span>{% trans "My Profile" %}</span></a></li>
|
||||
|
||||
|
||||
{% if request.user.is_superuser %}
|
||||
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'admin_settings' %}"><i class="fas fa-cog me-3 fs-5"></i> <span>{% trans "Staff Settings" %}</span></a></li>
|
||||
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'source_list' %}"><i class="fas fa-cog me-3 fs-5"></i> <span>{% trans "Integration Settings" %}</span></a></li>
|
||||
<li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'easy_logs' %}"><i class="fas fa-history me-3 fs-5"></i> <span>{% trans "Activity Log" %}</span></a></li>
|
||||
{% comment %} <li><a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none" href="#"><i class="fas fa-question-circle me-3 text-primary fs-5"></i> <span>{% trans "Help & Support" %}</span></a></li> {% endcomment %}
|
||||
<li class="nav-item me-3 dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal d-lg-none">
|
||||
{% if LANGUAGE_CODE == 'en' %}
|
||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
<button name="language" value="ar" class="btn bg-primary-theme text-white" type="submit">
|
||||
<span class="me-2">🇸🇦</span> العربية
|
||||
</button>
|
||||
</form>
|
||||
{% elif LANGUAGE_CODE == 'ar' %}
|
||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||
<button name="language" value="en" class="btn bg-primary-theme text-white" type="submit">
|
||||
<span class="me-2">🇺🇸</span> English
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</li>
|
||||
|
||||
{% comment %} CORRECTED LINKEDIN BLOCK {% endcomment %}
|
||||
{% if not request.session.linkedin_authenticated %}
|
||||
<li>
|
||||
<a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'linkedin_login' %}">
|
||||
<i class="fab fa-linkedin me-3 text-primary fs-5"></i>
|
||||
<span>{% trans "Connect LinkedIn" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="px-4 py-2 text-muted small">
|
||||
<i class="fab fa-linkedin text-primary me-2"></i>
|
||||
{% trans "LinkedIn Connected" %}
|
||||
</li>
|
||||
<li class="d-lg-none">
|
||||
<a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'message_list' %}">
|
||||
<i class="fas fa-envelope fs-5 me-3"></i> <span>{% trans "Messages" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<li>
|
||||
<a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'user_detail' request.user.pk %}">
|
||||
<i class="fas fa-user-circle me-3 fs-5"></i> <span>{% trans "My Profile" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% if request.user.is_superuser %}
|
||||
<li>
|
||||
<a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'settings' %}">
|
||||
<i class="fas fa-cog me-3 fs-5"></i> <span>{% trans "Settings" %}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<li><hr class="dropdown-divider my-1"></li>
|
||||
{% if request.user.is_authenticated %}
|
||||
|
||||
|
||||
<li>
|
||||
<form method="post" action="{% url 'account_logout'%}" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button
|
||||
type="submit"
|
||||
class="dropdown-item py-2 px-4 d-flex align-items-center border-0 bg-transparent text-start"
|
||||
class="dropdown-item py-2 px-4 d-flex align-items-center border-0 bg-transparent text-start w-100"
|
||||
aria-label="{% trans 'Sign out' %}"
|
||||
>
|
||||
<i class="fas fa-sign-out-alt me-3 fs-5 " style="color:red;"></i>
|
||||
<i class="fas fa-sign-out-alt me-3 fs-5" style="color:red;"></i>
|
||||
<span style="color:red;">{% trans "Sign Out" %}</span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@ -1415,8 +1415,8 @@ const elements = {
|
||||
</label>
|
||||
`;
|
||||
|
||||
// Add field input based on type
|
||||
if (field.type === 'text' || field.type === 'email' || field.type === 'phone' || field.type === 'date') {
|
||||
|
||||
if (field.type === 'text' || field.type === 'email' || field.type === 'phone' ||field.type === 'date') {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.className = 'field-input';
|
||||
|
||||
@ -146,12 +146,12 @@
|
||||
<div class="container-fluid py-4">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item "><a href="{% url 'job_detail' submission.template.job.slug %}" class="text-secondary text-decoration-none">Job Detail</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}" class="text-secondary">Form Template</a></li>
|
||||
<li class="breadcrumb-item "><a href="{% url 'job_detail' submission.template.job.slug %}" class="text-secondary text-decoration-none">{% trans "Job Detail" %}</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'form_builder' submission.template.pk%}" class="text-secondary text-decoration-none">{% trans "Form Template" %}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page" style="
|
||||
color: #F43B5E; /* Rosy Accent Color */
|
||||
font-weight: 600;
|
||||
">Submission Details</li>
|
||||
">{% trans "Submission Details" %}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row">
|
||||
|
||||
@ -182,6 +182,39 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% comment %} <div class="container py-4">
|
||||
<!-- Search and Filter Section -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" action="" class="w-100">
|
||||
<div class="row g-3 align-items-end">
|
||||
<div class="col-12 col-md-5">
|
||||
<label for="search" class="form-label small text-muted">{% trans "Search by name or Email" %}</label>
|
||||
<div class="input-group">
|
||||
{% include 'includes/search_form.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-2">
|
||||
<label for="date_from" class="form-label small text-muted">{% trans "From Date" %}</label>
|
||||
<input type="date" class="form-control" id="date_from" name="date_from" value="{{ request.GET.date_from }}">
|
||||
</div>
|
||||
<div class="col-12 col-md-2">
|
||||
<label for="date_to" class="form-label small text-muted">{% trans "To Date" %}</label>
|
||||
<input type="date" class="form-control" id="date_to" name="date_to" value="{{ request.GET.date_to }}">
|
||||
</div>
|
||||
<div class="col-12 col-md-3 d-flex gap-2">
|
||||
<button type="submit" class="btn btn-main-action flex-grow-1">
|
||||
<i class="fas fa-search me-1"></i> {% trans "Filter" %}
|
||||
</button>
|
||||
<a href="?" class="btn btn-outline-secondary flex-grow-1">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Clear" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endcomment %}
|
||||
<div class="container py-4">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
@ -254,7 +287,7 @@
|
||||
<div class="card-view">
|
||||
<div class="row g-4">
|
||||
{% for submission in page_obj %}
|
||||
<div class="col-12">
|
||||
<div class="col-md-6 col-lg-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<h3 class="h5 mb-2">{% trans "Submission" %} #{{ submission.id }}</h3>
|
||||
|
||||
@ -93,9 +93,9 @@
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* 5. BADGE VISIBILITY FIXES (Guaranteed Colors) */
|
||||
/* 5. badges VISIBILITY FIXES (Guaranteed Colors) */
|
||||
/* ---------------------------------------------------- */
|
||||
.badge {
|
||||
.badges {
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
padding: 0.4em 0.6em;
|
||||
@ -106,26 +106,26 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Dark Badges (CRUD Create/Delete & Login/Logout Type) - Use light text */
|
||||
.badge-crud-create {
|
||||
/* Dark badgess (CRUD Create/Delete & Login/Logout Type) - Use light text */
|
||||
.badges-crud-create {
|
||||
background-color: var(--color-success) !important;
|
||||
color: var(--color-text-on-dark) !important;
|
||||
}
|
||||
.badge-crud-delete {
|
||||
.badges-crud-delete {
|
||||
background-color: var(--color-danger) !important;
|
||||
color: var(--color-text-on-dark) !important;
|
||||
}
|
||||
.badge-login-status {
|
||||
.badges-login-status {
|
||||
background-color: var(--color-primary-dark) !important;
|
||||
color: var(--color-text-on-dark) !important;
|
||||
}
|
||||
|
||||
/* Light Badges (CRUD Update & Request Method) - Use dark text */
|
||||
.badge-crud-update {
|
||||
/* Light badgess (CRUD Update & Request Method) - Use dark text */
|
||||
.badges-crud-update {
|
||||
background-color: var(--color-warning) !important;
|
||||
color: var(--color-text-dark) !important;
|
||||
}
|
||||
.badge-request-method {
|
||||
.badges-request-method {
|
||||
background-color: var(--color-info) !important;
|
||||
color: var(--color-text-dark) !important;
|
||||
}
|
||||
@ -150,7 +150,7 @@
|
||||
{% block content %}
|
||||
<div class="container-fluid pt-5 pb-5 px-lg-5" style="background-color: var(--color-background-light);">
|
||||
<h1 class="h3 fw-bold dashboard-header mb-4 px-3">
|
||||
<i class="fas fa-shield-alt me-2" style="color: var(--color-primary);"></i>{% trans "System Audit Logs" %}
|
||||
<i class="fas fa-shield-alt me-2" style="color: var(--color-primary);"></i>{% trans "System Activity Logs" %}
|
||||
</h1>
|
||||
|
||||
<div class="alert summary-alert border-start border-5 p-3 mb-5 mx-3" role="alert">
|
||||
@ -228,9 +228,9 @@
|
||||
<td>{{ log.user.email|default:"N/A" }}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill
|
||||
{% if log.event_type == 1 %}badge-crud-create
|
||||
{% elif log.event_type == 2 %}badge-crud-update
|
||||
{% else %}badge-crud-delete{% endif %}">
|
||||
{% if log.event_type == 1 %}badges-crud-create
|
||||
{% elif log.event_type == 2 %}badges-crud-update
|
||||
{% else %}badges-crud-delete{% endif %}">
|
||||
{% if log.event_type == 1 %}<i class="fas fa-plus-circle me-1"></i>{% trans "CREATE" %}
|
||||
{% elif log.event_type == 2 %}<i class="fas fa-edit me-1"></i>{% trans "UPDATE" %}
|
||||
{% else %}<i class="fas fa-trash-alt me-1"></i>{% trans "DELETE" %}{% endif %}
|
||||
@ -256,7 +256,7 @@
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill badge-login-status">
|
||||
<span class="badge rounded-pill badges-login-status">
|
||||
{% if log.login_type == 0 %}{% trans "Login" %}
|
||||
{% elif log.login_type == 1 %}{% trans "Logout" %}
|
||||
{% else %}{% trans "Failed Login" %}{% endif %}
|
||||
@ -277,7 +277,7 @@
|
||||
<td>{{ log.datetime|date:"Y-m-d H:i:s" }}</td>
|
||||
<td>{{ log.user.get_full_name|default:log.user.email|default:"Anonymous" }}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill badge-request-method">{{ log.method }}</span>
|
||||
<span class="badge rounded-pill badges-request-method">{{ log.method }}</span>
|
||||
</td>
|
||||
<td><code class="text-break small" style="color: var(--color-text-dark) !important;">{{ log.url}}</code></td>
|
||||
|
||||
|
||||
@ -32,11 +32,17 @@
|
||||
{% trans "To" %}
|
||||
</label>
|
||||
<div class="border rounded p-3 bg-light" style="max-height: 200px; overflow-y: auto;">
|
||||
{% for choice in form.to %}
|
||||
{% for choice in form.to|slice:":1" %}
|
||||
<div class="form-check mb-2">
|
||||
{{ choice }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if form.to|length > 0 %}
|
||||
<div class="text-muted small mt-2">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{% blocktrans count total=form.to|length %}{{ total }} recipient selected{% plural %}{{ total }} recipients selected{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if form.to.errors %}
|
||||
<div class="text-danger small mt-1">
|
||||
|
||||
@ -85,8 +85,8 @@
|
||||
.bg-confirmed { background-color: var(--kaauh-info) !important; color: white; }
|
||||
.bg-cancelled { background-color: var(--kaauh-danger) !important; color: white; }
|
||||
.bg-completed { background-color: var(--kaauh-success) !important; color: white; }
|
||||
.bg-remote { background-color: #007bff !important; color: white; }
|
||||
.bg-onsite { background-color: #6f42c1 !important; color: white; }
|
||||
.bg-remote { background-color: #004a53 color: white; }
|
||||
.bg-onsite { background-color: #00636e !important; color: white; }
|
||||
|
||||
/* Timeline Styling */
|
||||
.timeline {
|
||||
@ -206,7 +206,6 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<!-- Header Section -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
@ -224,18 +223,18 @@
|
||||
<a href="{% url 'job_detail' interview.job.slug %}" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-briefcase me-1"></i> {% trans "View Job" %}
|
||||
</a>
|
||||
{% if interview.status != 'cancelled' %}
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#statusModal">
|
||||
<i class="fas fa-redo-alt me-1"></i> {% trans "Update Interview status" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Left Column - Candidate & Interview Info -->
|
||||
<div class="col-lg-8">
|
||||
<!-- Candidate Information Panel -->
|
||||
<div class="kaauh-card shadow-sm p-4 mb-4">
|
||||
<div class="d-flex align-items-start justify-content-between mb-3">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark); font-weight: 600;">
|
||||
@ -276,7 +275,7 @@
|
||||
<p class="mb-2"><strong>{% trans "Department:" %}</strong> {{ interview.job.department }}</p>
|
||||
<p class="mb-2"><strong>{% trans "Applied Date:" %}</strong> {{ interview.application.created_at|date:"d-m-Y" }}</p>
|
||||
<p class="mb-0"><strong>{% trans "Current Stage:" %}</strong>
|
||||
<span class="badge stage-badge stage-{{ interview.application.stage|lower }}">
|
||||
<span class="badge stage-badge bg-primary-theme">
|
||||
{{ interview.application.stage }}
|
||||
</span>
|
||||
</p>
|
||||
@ -285,7 +284,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Interview Details Panel -->
|
||||
<div class="kaauh-card shadow-sm p-4 mb-4">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<h5 class="mb-0" style="color: var(--kaauh-teal-dark); font-weight: 600;">
|
||||
@ -384,7 +382,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timeline/History Section -->
|
||||
<div class="kaauh-card shadow-sm p-4">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); font-weight: 600;">
|
||||
<i class="fas fa-history me-2"></i> {% trans "Interview Timeline" %}
|
||||
@ -395,13 +392,13 @@
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="mb-1">{% trans "Interview Scheduled" %}</h6>
|
||||
<p class="mb-0 text-muted">{% trans "Interview was scheduled for" %} {{ interview.interview_date|date:"d-m-Y" }} {{ interview.interview_time|date:"h:i A" }}</p>
|
||||
<p class="mb-0 text-muted">{% trans "Interview was scheduled for" %} {{ interview.interview_date|date:"F j, Y" }} {{ interview.interview_time|date:"h:i A" }}</p>
|
||||
</div>
|
||||
<small class="text-muted">{{ interview.interview.created_at|date:"d-m-Y h:i A" }}</small>
|
||||
<small class="text-muted">{{ interview.interview.created_at|date:"F j,Y h:i A" }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if interview.interview.status == 'CONFIRMED' %}
|
||||
{% if interview.status == 'confirmed' %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
@ -414,7 +411,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if interview.interview.status == 'COMPLETED' %}
|
||||
{% if interview.status == 'completed' %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
@ -427,15 +424,15 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if interview.interview.status == 'CANCELLED' %}
|
||||
{% if interview.status == 'cancelled' %}
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-content">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<h6 class="mb-1">{% trans "Interview Cancelled" %}</h6>
|
||||
<p class="mb-0 text-muted">{% trans "Interview was cancelled" %}</p>
|
||||
<p class="mb-0 text-muted">{% trans "Interview was cancelled on: " %}{{interview.interview.cancelled_at|date:"F j, Y"}}</p>
|
||||
</div>
|
||||
<small class="text-muted">{% trans "Recently" %}</small>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -444,15 +441,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Column - Participants & Actions -->
|
||||
<div class="col-lg-4">
|
||||
<!-- Participants Panel -->
|
||||
<div class="kaauh-card shadow-sm p-4 mb-4">
|
||||
{% comment %} <div class="kaauh-card shadow-sm p-4 mb-4">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); font-weight: 600;">
|
||||
<i class="fas fa-users me-2"></i> {% trans "Participants" %}
|
||||
</h5>
|
||||
|
||||
<!-- Internal Participants -->
|
||||
{% if interview.participants.exists %}
|
||||
<h6 class="mb-2 text-muted">{% trans "Internal Participants" %}</h6>
|
||||
{% for participant in interview.participants.all %}
|
||||
@ -468,7 +462,6 @@
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- External Participants -->
|
||||
{% if interview.system_users.exists %}
|
||||
<h6 class="mb-2 mt-3 text-muted">{% trans "External Participants" %}</h6>
|
||||
{% for user in interview.system_users.all %}
|
||||
@ -496,49 +489,71 @@
|
||||
data-bs-target="#participantModal">
|
||||
<i class="fas fa-user-plus me-1"></i> {% trans "Add Participants" %}
|
||||
</button>
|
||||
</div>
|
||||
</div> {% endcomment %}
|
||||
|
||||
<!-- Actions Panel -->
|
||||
<div class="kaauh-card shadow-sm p-4">
|
||||
<h5 class="mb-3" style="color: var(--kaauh-teal-dark); font-weight: 600;">
|
||||
<i class="fas fa-cog me-2"></i> {% trans "Actions" %}
|
||||
</h5>
|
||||
|
||||
<div class="action-buttons">
|
||||
{% if interview.status != 'CANCELLED' and interview.status != 'COMPLETED' %}
|
||||
|
||||
{% if interview.status != 'cancelled' and interview.status != 'COMPLETED' %}
|
||||
{# 1. Interview is Active (SCHEDULED/CONFIRMED) #}
|
||||
<button type="button" class="btn btn-main-action btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#rescheduleModal">
|
||||
<i class="fas fa-redo-alt me-1"></i> {% trans "Reschedule" %}
|
||||
</button>
|
||||
|
||||
<button type="button" class="btn btn-outline-warning btn-sm"
|
||||
<button type="button" class="btn btn-outline-danger btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#cancelModal">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel Interview" %}
|
||||
</button>
|
||||
|
||||
{% elif interview.status == 'cancelled' %}
|
||||
{# 2. Interview is CANCELLED (Promote Reschedule) #}
|
||||
<div class="alert alert-danger w-100 py-2 small" role="alert">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
{% trans "Interview is CANCELLED." %}
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info py-2 small" role="alert" style="border-left: 5px solid #00636e; background-color: #f0f8ff;">
|
||||
{# Use an icon for visual appeal #}
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>{% trans "Reason:" %}</strong>
|
||||
{{ interview.interview.cancelled_reason }}
|
||||
|
||||
</div>
|
||||
|
||||
{% elif interview.status == 'COMPLETED' %}
|
||||
{# 3. Interview is COMPLETED (Promote Result Update) #}
|
||||
<div class="alert alert-success w-100 py-2 small" role="alert">
|
||||
<i class="fas fa-check-circle me-1"></i>
|
||||
{% trans "Interview is COMPLETED. Update the final result." %}
|
||||
</div>
|
||||
<button type="button" class="btn btn-success btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#resultModal"
|
||||
style="width: 100%;">
|
||||
<i class="fas fa-clipboard-check me-1"></i> {% trans "Update Result" %}
|
||||
</button>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<button type="button" class="btn btn-outline-primary btn-sm"
|
||||
<hr class="w-100 mt-2 mb-2">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm w-100"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#emailModal">
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Send Email" %}
|
||||
</button>
|
||||
|
||||
{% if interview.status == 'COMPLETED' %}
|
||||
<button type="button" class="btn btn-outline-success btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#resultModal">
|
||||
<i class="fas fa-check-circle me-1"></i> {% trans "Update Result" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Candidate Profile Modal -->
|
||||
<div class="modal fade modal-xl" id="candidateModal" tabindex="-1" aria-labelledby="candidateModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content kaauh-card">
|
||||
@ -558,7 +573,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Participant Modal -->
|
||||
<div class="modal fade" id="participantModal" tabindex="-1" aria-labelledby="participantModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content kaauh-card">
|
||||
@ -574,14 +588,12 @@
|
||||
<div class="mb-3">
|
||||
<label for="internal_participants" class="form-label">{% trans "Internal Participants" %}</label>
|
||||
<select multiple class="form-select" id="internal_participants" name="participants">
|
||||
<!-- Options will be populated dynamically -->
|
||||
</select>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="external_participants" class="form-label">{% trans "External Participants" %}</label>
|
||||
<select multiple class="form-select" id="external_participants" name="system_users">
|
||||
<!-- Options will be populated dynamically -->
|
||||
</select>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-plus me-1"></i> {% trans "Add Participants" %}
|
||||
@ -592,7 +604,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Email Modal -->
|
||||
<div class="modal fade" id="emailModal" tabindex="-1" aria-labelledby="emailModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content kaauh-card">
|
||||
@ -616,23 +627,23 @@
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="email_message" class="form-label">{% trans "Message" %}</label>
|
||||
<textarea class="form-control" id="email_message" name="message" rows="6">
|
||||
{% trans "Dear" %} {{ interview.application.name }},
|
||||
<textarea class="form-control" id="email_message" name="message" rows="6">
|
||||
{% trans "Dear" %} {{ interview.application.name }},
|
||||
|
||||
{% trans "Your interview details are as follows:" %}
|
||||
{% trans "Your interview details are as follows:" %}
|
||||
|
||||
{% trans "Date:" %} {{ interview.interview_date|date:"d-m-Y" }}
|
||||
{% trans "Time:" %} {{ interview.interview_time|date:"h:i A" }}
|
||||
{% trans "Job:" %} {{ interview.job.title }}
|
||||
{% trans "Date:" %} {{ interview.interview_date|date:"d-m-Y" }}
|
||||
{% trans "Time:" %} {{ interview.interview_time|date:"h:i A" }}
|
||||
{% trans "Job:" %} {{ interview.job.title }}
|
||||
|
||||
{% if interview.interview.location_type == 'Remote' %}
|
||||
{% trans "This is a remote interview. You will receive the meeting link separately." %}
|
||||
{% else %}
|
||||
{% trans "This is an onsite interview. Please arrive 10 minutes early." %}
|
||||
{% endif %}
|
||||
{% if interview.interview.location_type == 'Remote' %}
|
||||
{% trans "This is a remote interview. You will receive the meeting link separately." %}
|
||||
{% else %}
|
||||
{% trans "This is an onsite interview. Please arrive 10 minutes early." %}
|
||||
{% endif %}
|
||||
|
||||
{% trans "Best regards," %}
|
||||
{% trans "HR Team" %}
|
||||
{% trans "Best regards," %}
|
||||
{% trans "HR Team" %}
|
||||
</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
@ -644,7 +655,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Reschedule Modal -->
|
||||
<div class="modal fade" id="rescheduleModal" tabindex="-1" aria-labelledby="rescheduleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content kaauh-card">
|
||||
@ -657,20 +667,9 @@
|
||||
<div class="modal-body">
|
||||
<form method="post" action="{% url 'reschedule_meeting_for_application' interview.slug %}">
|
||||
{% csrf_token %}
|
||||
{{reschedule_form|crispy}}
|
||||
{% comment %} <div class="mb-3">
|
||||
<label for="topic" class="form-label">{% trans "topic" %}</label>
|
||||
<input type="text" class="form-control" id="topic" name="topic" value="{{interview.interview.topic}}" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="start_time" class="form-label">{% trans "Start Time" %}</label>
|
||||
<input type="datetime-local" class="form-control" id="start_time" name="start_time" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="duration" class="form-label">{% trans "Duration" %}</label>
|
||||
<input type="number" class="form-control" id="duration" name="duration" required>
|
||||
</div> {% endcomment %}
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
{{ reschedule_form|crispy }}
|
||||
|
||||
<button type="submit" class="btn btn-main-action btn-sm mt-3">
|
||||
<i class="fas fa-redo-alt me-1"></i> {% trans "Reschedule" %}
|
||||
</button>
|
||||
</form>
|
||||
@ -679,7 +678,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cancel Modal -->
|
||||
<div class="modal fade" id="cancelModal" tabindex="-1" aria-labelledby="cancelModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content kaauh-card">
|
||||
@ -690,19 +688,20 @@
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" action="{% url 'cancel_interview_for_application' interview.slug %}">
|
||||
<form method="post" action="{% url 'cancel_interview_for_application' interview.interview.slug %}">
|
||||
{% csrf_token %}
|
||||
{{ cancel_form|crispy }}
|
||||
<div class="alert alert-warning">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
{% trans "Are you sure you want to cancel this interview? This action cannot be undone." %}
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-danger btn-sm">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel Interview" %}
|
||||
</button>
|
||||
<div class="d-flex gap-2 justify-content-end">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-danger btn-sm">
|
||||
<i class="fas fa-times me-1"></i> {% trans "Cancel Interview" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -710,7 +709,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Result Modal -->
|
||||
<div class="modal fade" id="resultModal" tabindex="-1" aria-labelledby="resultModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content kaauh-card">
|
||||
@ -737,7 +735,7 @@
|
||||
<textarea class="form-control" id="result_notes" name="notes" rows="4"
|
||||
placeholder="{% trans 'Add interview feedback and notes' %}"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
<button type="submit" class="btn btn-main-action btn-sm mt-2">
|
||||
<i class="fas fa-check me-1"></i> {% trans "Update Result" %}
|
||||
</button>
|
||||
</form>
|
||||
@ -746,7 +744,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Update Status Modal -->
|
||||
<div class="modal fade" id="statusModal" tabindex="-1" aria-labelledby="statusModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content kaauh-card">
|
||||
@ -759,14 +756,14 @@
|
||||
<div class="modal-body">
|
||||
<form method="post" action="{% url 'update_interview_status' interview.slug %}">
|
||||
{% csrf_token %}
|
||||
{{interview_status_form|crispy}}
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-check me-1"></i> {% trans "Update Status" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">
|
||||
{{ interview_status_form|crispy }}
|
||||
<div class="d-flex gap-2 mt-3 justify-content-end">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-main-action btn-sm">
|
||||
<i class="fas fa-check me-1"></i> {% trans "Update Status" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -780,7 +777,7 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Clear modal content when hidden
|
||||
const modals = ['candidateModal', 'participantModal', 'emailModal', 'rescheduleModal', 'cancelModal', 'resultModal'];
|
||||
const modals = ['candidateModal', 'participantModal', 'emailModal', 'rescheduleModal', 'cancelModal', 'resultModal', 'statusModal'];
|
||||
|
||||
modals.forEach(modalId => {
|
||||
const modal = document.getElementById(modalId);
|
||||
@ -788,6 +785,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
modal.addEventListener('hidden.bs.modal', function () {
|
||||
const modalBody = modal.querySelector('.modal-body');
|
||||
if (modalBody && modalId === 'candidateModal') {
|
||||
// Reset content for AJAX-loaded Candidate Modal
|
||||
modalBody.innerHTML = `
|
||||
<div class="text-center py-5 text-muted">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i><br>
|
||||
@ -795,9 +793,12 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
// Note: For forms using Django/Crispy, you might need extra JS
|
||||
// to explicitly reset form fields if the form is not reloading
|
||||
// when the modal is closed.
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -218,7 +218,9 @@
|
||||
<div class="card-body">
|
||||
|
||||
<h5 class="text-muted mb-3">{% trans "Administrative & Location" %}
|
||||
{% if job.status == 'DRAFT'%}
|
||||
<a href="{% url 'job_update' job.slug %}" class="btn btn-main-action btn-sm"><li class="fa fa-edit"></li>{% trans "Edit Job" %}</a>
|
||||
{% endif %}
|
||||
<div class="float-end">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-user-tie me-2 text-primary"></i> <strong>{% trans "Assigned to :" %} </strong> {{ job.assigned_to|default:"" }}
|
||||
@ -331,14 +333,21 @@
|
||||
<a href="{% url 'applications_screening_view' job.slug %}" class="btn btn-main-action">
|
||||
<i class="fas fa-layer-group me-1"></i> {% trans "Manage Applications" %}
|
||||
</a>
|
||||
|
||||
<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" %}
|
||||
</a>
|
||||
|
||||
<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" %}
|
||||
</a>
|
||||
|
||||
{% if not job.form_template.is_active %}
|
||||
{% if not jobzip_created %}
|
||||
<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" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if job.zip_created %}
|
||||
<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" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>{% trans "Bulk CV dowload is inactive. To activate please change the status of the job to CLOSED." %}</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -252,7 +252,7 @@
|
||||
</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if user.is_staff %}
|
||||
{% comment %} {% if user.is_staff %}
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'person_update' person.slug %}" class="btn btn-light">
|
||||
<i class="fas fa-edit me-1"></i> {% trans "Edit Applicant" %}
|
||||
@ -261,7 +261,7 @@
|
||||
<i class="fas fa-trash-alt me-1"></i> {% trans "Delete" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %} {% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -257,13 +257,60 @@
|
||||
|
||||
<form method="post" action="{% url 'person_update' person.slug %}" enctype="multipart/form-data" id="person-form">
|
||||
{% csrf_token %}
|
||||
{{form|crispy}}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.first_name.id_for_label }}">{{ form.first_name.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.first_name %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.middle_name.id_for_label }}">{{ form.middle_name.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.middle_name %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.last_name.id_for_label }}">{{ form.last_name.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.last_name %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.email.id_for_label }}">{{ form.email.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.email %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.phone.id_for_label }}">{{ form.phone.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.phone %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.gpa.id_for_label }}">{{ form.gpa.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.gpa %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.national_id.id_for_label }}">{{ form.national_id.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.national_id %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.date_of_birth.id_for_label }}">{{ form.date_of_birth.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.date_of_birth %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.nationality.id_for_label }}">{{ form.nationality.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.nationality %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label for="{{ form.gender.id_for_label }}">{{ form.gender.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.gender %}
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label for="{{ form.address.id_for_label }}">{{ form.address.label }}</label>
|
||||
{% include "bootstrap5/field.html" with field=form.address %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
<div class="d-flex gap-2">
|
||||
<div class="d-flex gap-2">
|
||||
<button form="person-form" type="submit" class="btn btn-main-action">
|
||||
<i class="fas fa-save me-1"></i> {% trans "Update" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -152,10 +152,15 @@
|
||||
<a class="nav-link text-white" href="{% url 'user_detail' request.user.pk %}">
|
||||
<i class="fas fa-user-circle me-1"></i> <span>{% trans "My Profile" %}</span></a></li>
|
||||
{% endif %}
|
||||
<li class="nav-item me-2">
|
||||
<a class="nav-link text-white" href="{% url 'message_list' %}">
|
||||
<i class="fas fa-envelope"></i> <span>{% trans "Messages" %}</span>
|
||||
</a>
|
||||
<li class="nav-item mx-2 mt-2">
|
||||
<a href="{% url 'message_list' %}"
|
||||
class=" btn btn-sm btn-outline-warning position-relative">
|
||||
<i class="fas fa-envelope me-1"></i>
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||
{{ request.user.get_unread_message_count }}
|
||||
</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="nav-item ms-3">
|
||||
|
||||
@ -136,7 +136,7 @@
|
||||
<div class="mb-3">
|
||||
<label class="text-muted small">{% trans "Status" %}</label>
|
||||
<div>
|
||||
<span class="status-badge status-{{ assignment.status }}">
|
||||
<span class="status-badge bg-primary-theme text-white">
|
||||
{{ assignment.get_status_display }}
|
||||
</span>
|
||||
</div>
|
||||
@ -261,7 +261,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge bg-info">{{ application.get_stage_display }}</span>
|
||||
<span class="badge bg-primary-theme">{{ application.get_stage_display }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="small text-muted">
|
||||
@ -271,7 +271,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-4">
|
||||
<span class="badge bg-soft-info text-info rounded-pill px-3">
|
||||
<span class="badge bg-primary-theme px-3">
|
||||
{{application.get_stage_display }}</span>
|
||||
</td>
|
||||
<td class="px-4">
|
||||
@ -339,9 +339,9 @@
|
||||
<div class="text-muted">/ {{ assignment.max_candidates }} {% trans "applications" %}</div>
|
||||
</div>
|
||||
|
||||
<div class="progress mt-3" style="height: 8px;">
|
||||
<div class="progress mt-3 " style="height: 8px;">
|
||||
{% widthratio total_applications assignment.max_candidates 100 as progress %}
|
||||
<div class="progress-bar" style="width: {{ progress }}%"></div>
|
||||
<div class="progress-bar bg-primary-theme" style="width: {{ progress }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -354,7 +354,7 @@
|
||||
</h5>
|
||||
|
||||
<div class="d-grid gap-2">
|
||||
<a href="{}"
|
||||
<a href="{% url "message_list" %}"
|
||||
class="btn btn-outline-primary">
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Send Message" %}
|
||||
</a>
|
||||
|
||||
@ -43,10 +43,10 @@
|
||||
border-radius: 0.35rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
.status-ACTIVE { background-color: var(--kaauh-success); color: white; }
|
||||
{% comment %} .status-ACTIVE { background-color: var(--kaauh-success); color: white; }
|
||||
.status-EXPIRED { background-color: var(--kaauh-danger); color: white; }
|
||||
.status-COMPLETED { background-color: var(--kaauh-info); color: white; }
|
||||
.status-CANCELLED { background-color: var(--kaauh-warning); color: #856404; }
|
||||
.status-CANCELLED { background-color: var(--kaauh-warning); color: #856404; } {% endcomment %}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@ -130,7 +130,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="badge bg-primary me-2">{{ assignment.submitted_count }}</span>
|
||||
<span class="badge bg-primary-theme text-white me-2">{{ assignment.applications_submited_count}}</span>
|
||||
<span class="text-muted">/ {{ assignment.max_candidates }}</span>
|
||||
</div>
|
||||
<div class="progress mt-1" style="height: 4px;">
|
||||
@ -150,7 +150,7 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<span class="status-badge status-{{ assignment.status }}">
|
||||
<span class="status-badge bg-primary-theme text-white">
|
||||
{{ assignment.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
@ -509,6 +509,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-grid gap-2 mt-4">
|
||||
<a href="{% url 'regenerate_agency_password' agency.slug %}"
|
||||
class="btn btn-danger"
|
||||
title="{% trans 'This action resets the agency\'s current login password.' %}"
|
||||
onclick="return confirm('{% trans "Are you sure you want to regenerate the password for this agency? This cannot be undone." %}');"
|
||||
>
|
||||
<i class="fas fa-key me-2"></i>
|
||||
{% trans "Regenerate Agency Password" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
@ -531,6 +542,7 @@
|
||||
aria-selected="true"
|
||||
>
|
||||
<i class="fas fa-users me-1"></i>
|
||||
<span>({{total_applications}})</span>
|
||||
{% trans "Recent Applications" %}
|
||||
</button>
|
||||
</li>
|
||||
@ -546,6 +558,7 @@
|
||||
aria-selected="false"
|
||||
>
|
||||
<i class="fas fa-briefcase me-1"></i>
|
||||
<span>({{total_job_assignments}})</span>
|
||||
{% trans "Assigned Jobs" %}
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@ -52,12 +52,8 @@
|
||||
{% comment %} <a href="{% url 'agency_portal_submit_application' %}" class="btn btn-main-action me-2">
|
||||
<i class="fas fa-user-plus me-1"></i> {% trans "Submit Application" %}
|
||||
</a>
|
||||
<a href="#" class="btn btn-outline-secondary position-relative">
|
||||
<i class="fas fa-envelope me-1"></i> {% trans "Messages" %}
|
||||
{% if total_unread_messages > 0 %}
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||
{{ total_unread_messages }}
|
||||
</span>
|
||||
|
||||
>
|
||||
{% endif %}
|
||||
</a> {% endcomment %}
|
||||
</div>
|
||||
@ -207,15 +203,15 @@
|
||||
class="btn btn-sm btn-main-action">
|
||||
<i class="fas fa-eye me-1"></i> {% trans "View Details" %}
|
||||
</a>
|
||||
{% if stats.unread_messages > 0 %}
|
||||
<a href="{% url 'agency_portal_assignment_detail' stats.assignment.slug %}#messages"
|
||||
{% comment %} {% if stats.unread_messages > 0 %}
|
||||
<a href="{% url 'message_list' %}"
|
||||
class="btn btn-sm btn-outline-warning position-relative">
|
||||
<i class="fas fa-envelope me-1"></i>
|
||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
|
||||
{{ stats.unread_messages }}
|
||||
</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %} {% endcomment %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -326,6 +326,7 @@
|
||||
<div class="modal-body" id="personModalBody">
|
||||
<form id="person_form" hx-post="{% url 'person_create' %}" hx-vals='{"view":"portal","agency":"{{ agency.slug }}"}' hx-select=".persons-list" hx-target=".persons-list" hx-swap="outerHTML"
|
||||
hx-on:afterRequest="$('#personModal').modal('hide')">
|
||||
|
||||
{% csrf_token %}
|
||||
<div class="row g-4">
|
||||
<div class="col-md-4">
|
||||
@ -346,6 +347,14 @@
|
||||
{{ person_form.phone|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.gpa|as_crispy_field }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{{ person_form.national_id|as_crispy_field }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
{{ person_form.date_of_birth|as_crispy_field }}
|
||||
|
||||
@ -106,6 +106,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="{{ form.gpa.id_for_label }}" class="form-label">
|
||||
{% trans "GPA" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.gpa }}
|
||||
{% if form.gpa.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.gpa.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="{{ form.national_id.id_for_label }}" class="form-label">
|
||||
{% trans "National Id Or Iqama" %} <span class="text-danger">*</span>
|
||||
</label>
|
||||
{{ form.national_id }}
|
||||
{% if form.national_id.errors %}
|
||||
<div class="text-danger small">
|
||||
{{ form.national_id.errors.0 }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
||||
|
||||
@ -1,68 +1,395 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static i18n %}
|
||||
|
||||
{% block title %}{% trans "Confirm Delete" %} - {{ block.super }}{% endblock %}
|
||||
{% block title %}{% trans "Delete Application" %} - {{ block.super }}{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* KAAT-S UI Variables */
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-border: #eaeff3;
|
||||
--kaauh-primary-text: #343a40;
|
||||
--kaauh-danger: #dc3545;
|
||||
--kaauh-warning: #ffc107;
|
||||
}
|
||||
|
||||
/* Main Container & Card Styling */
|
||||
.kaauh-card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* Warning Section */
|
||||
.warning-section {
|
||||
background: linear-gradient(135deg, #fff3cd 0%, #ffeeba 100%);
|
||||
border: 1px solid #ffeeba;
|
||||
border-radius: 0.75rem;
|
||||
padding: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 4rem;
|
||||
color: var(--kaauh-warning);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.warning-title {
|
||||
color: #856404;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.warning-text {
|
||||
color: #856404;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Info Card */
|
||||
.app-info {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0.75rem;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid var(--kaauh-border);
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
border-radius: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: 600;
|
||||
color: var(--kaauh-primary-text);
|
||||
margin-bottom: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: #6c757d;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/* Button Styling */
|
||||
.btn-danger {
|
||||
background-color: var(--kaauh-danger);
|
||||
border-color: var(--kaauh-danger);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
border-color: #bd2130;
|
||||
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Consequence List */
|
||||
.consequence-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.consequence-list li {
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.consequence-list li:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.consequence-list li i {
|
||||
color: var(--kaauh-danger);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Candidate Info Style */
|
||||
.candidate-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
border: 3px solid var(--kaauh-teal);
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background-color: #e9ecef;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3px solid var(--kaauh-teal);
|
||||
}
|
||||
|
||||
/* Heroicon adjustments to match font-awesome size */
|
||||
.heroicon {
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
vertical-align: -0.125em;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<div>
|
||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
{% trans "Delete Application" %}
|
||||
</h1>
|
||||
<p class="text-muted mb-0">
|
||||
{% trans "You are about to delete an Application record. This action cannot be undone." %}
|
||||
</p>
|
||||
</div>
|
||||
{# Assuming application_detail URL takes object.pk or object.slug #}
|
||||
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary">
|
||||
<i class="fas fa-arrow-left me-1"></i> {% trans "Back to Application" %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8 col-xl-6">
|
||||
<div class="card border-danger shadow-lg">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h2 class="h4 mb-0 d-flex align-items-center">
|
||||
<svg class="heroicon me-2" viewBox="0 0 24 24" width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10 11V17"></path>
|
||||
<path d="M14 11V17"></path>
|
||||
<path d="M4 7H20"></path>
|
||||
<path d="M6 7H18V19C18 19.5304 17.7893 20.0391 17.4142 20.4142C17.0391 20.7893 16.5304 21 16 21H8C7.46957 21 6.96086 20.7893 6.58579 20.4142C6.21071 20.0391 6 19.5304 6 19V7Z"></path>
|
||||
<path d="M9 7V4C9 3.46957 9.21071 2.96086 9.58579 2.58579C9.96086 2.21071 10.4696 2 11 2H13C13.5304 2 14.0391 2.21071 14.4142 2.58579C14.7893 2.96086 15 3.46957 15 4V7"></path>
|
||||
</svg>
|
||||
{% trans "Confirm Deletion" %}
|
||||
</h2>
|
||||
<div class="col-lg-8">
|
||||
<div class="warning-section">
|
||||
<div class="warning-icon">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
</div>
|
||||
<h3 class="warning-title">{% trans "Warning: This action cannot be undone!" %}</h3>
|
||||
<p class="warning-text">
|
||||
{% blocktrans with candidate_name=object.candidate.full_name job_title=object.job.title %}
|
||||
Deleting the application submitted by **{{ candidate_name }}** for the job **{{ job_title }}** will permanently remove all associated data. Please review the information below carefully before proceeding.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<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-file-alt me-2"></i>
|
||||
{% trans "Application to be Deleted" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="lead text-danger fw-bold">
|
||||
{% trans "You are about to permanently delete the following Application." %}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% blocktrans with candidate_name=object.candidate.full_name job_title=object.job.title %}
|
||||
**Are you absolutely sure** you want to delete the application submitted by **{{ object}}** ?
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
<blockquote class="blockquote border-start border-danger border-4 ps-3 py-2 bg-light rounded-end">
|
||||
<h5 class="mb-1 text-dark">{% trans "Application Details" %}</h5>
|
||||
<div class="app-info">
|
||||
{% if object.candidate %}
|
||||
<p class="mb-0"><strong>{% trans "Candidate:" %}</strong> {{ object.candidate.full_name }}</p>
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
{# Assuming candidate has a profile_image field #}
|
||||
{% if object.candidate.profile_image %}
|
||||
<img src="{{ object.candidate.profile_image.url }}" alt="{{ object.candidate.full_name }}" class="candidate-avatar me-3">
|
||||
{% else %}
|
||||
<div class="avatar-placeholder me-3">
|
||||
<i class="fas fa-user text-muted fa-2x"></i>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h4 class="mb-1">{{ object.candidate.full_name }}</h4>
|
||||
{% if object.candidate.email %}
|
||||
<p class="text-muted mb-0">{{ object.candidate.email }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if object.job %}
|
||||
<p class="mb-0"><strong>{% trans "Job Title:" %}</strong> {{ object.job.title }}</p>
|
||||
{% endif %}
|
||||
<p class="mb-0"><strong>{% trans "Applied On:" %}</strong> {{ object.created_at|date:"M d, Y \a\t P" }}</p>
|
||||
</blockquote>
|
||||
|
||||
<p class="mt-4 text-muted">
|
||||
{% trans "This action is **irreversible** and all associated data will be lost." %}
|
||||
</p>
|
||||
|
||||
{% if object.job %}
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-briefcase"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Job Title" %}</div>
|
||||
<div class="info-value">{{ object.job.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-calendar-alt"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Applied On" %}</div>
|
||||
<div class="info-value">{{ object.created_at|date:"F d, Y \a\t P" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if object.status %}
|
||||
<div class="info-item">
|
||||
<div class="info-icon">
|
||||
<i class="fas fa-cogs"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<div class="info-label">{% trans "Current Status" %}</div>
|
||||
<div class="info-value">{{ object.get_status_display }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex justify-content-between align-items-center">
|
||||
<a href="{% url 'application_list' %}" class="btn btn-secondary">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<form method="post">
|
||||
</div>
|
||||
|
||||
<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-list me-2"></i>
|
||||
{% trans "What will happen when you delete this Application?" %}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="consequence-list">
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "The Application record and all status history will be permanently deleted." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "All associated screening results, scores, and evaluations will be removed." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "Any linked documents (CV, cover letter) specific to this application will be lost." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "The Candidate will still exist, but this specific application history will be lost." %}
|
||||
</li>
|
||||
<li>
|
||||
<i class="fas fa-times-circle"></i>
|
||||
{% trans "This action cannot be undone under any circumstances." %}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card kaauh-card">
|
||||
<div class="card-body">
|
||||
<form method="post" id="deleteForm">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger d-flex align-items-center">
|
||||
<svg class="heroicon me-1" viewBox="0 0 24 24" width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
{% trans "Yes, Permanently Delete" %}
|
||||
</button>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="confirm_delete" name="confirm_delete" required>
|
||||
<label class="form-check-label" for="confirm_delete">
|
||||
<strong>{% trans "I understand that this action cannot be undone and I want to permanently delete this application." %}</strong>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'application_detail' object.pk %}" class="btn btn-secondary btn-lg">
|
||||
<i class="fas fa-times me-2"></i>
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="btn btn-danger btn-lg"
|
||||
id="deleteButton"
|
||||
disabled>
|
||||
<i class="fas fa-trash me-2"></i>
|
||||
{% trans "Delete Application Permanently" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const confirmDeleteCheckbox = document.getElementById('confirm_delete');
|
||||
const deleteButton = document.getElementById('deleteButton');
|
||||
const deleteForm = document.getElementById('deleteForm');
|
||||
|
||||
function validateForm() {
|
||||
const checkboxChecked = confirmDeleteCheckbox.checked;
|
||||
deleteButton.disabled = !checkboxChecked;
|
||||
|
||||
// Toggle button classes for visual feedback
|
||||
if (checkboxChecked) {
|
||||
deleteButton.classList.remove('btn-secondary');
|
||||
deleteButton.classList.add('btn-danger');
|
||||
} else {
|
||||
deleteButton.classList.remove('btn-danger');
|
||||
deleteButton.classList.add('btn-secondary');
|
||||
}
|
||||
}
|
||||
|
||||
confirmDeleteCheckbox.addEventListener('change', validateForm);
|
||||
// Initialize state on page load
|
||||
validateForm();
|
||||
|
||||
// Add confirmation and prevent double submission before final submission
|
||||
deleteForm.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const candidateName = "{{ object.candidate.full_name|escapejs }}";
|
||||
const jobTitle = "{{ object.job.title|escapejs }}";
|
||||
|
||||
// Construct a confirmation message using string replacement for safety
|
||||
const confirmationMessageTemplate = "{% blocktrans with candidate_name='CANDIDATE_PLACEHOLDER' job_title='JOB_PLACEHOLDER' %}Are you absolutely sure you want to permanently delete the application by CANDIDATE_PLACEHOLDER for JOB_PLACEHOLDER? This action cannot be reversed!{% endblocktrans %}";
|
||||
|
||||
const confirmationMessage = confirmationMessageTemplate
|
||||
.replace('CANDIDATE_PLACEHOLDER', candidateName)
|
||||
.replace('JOB_PLACEHOLDER', jobTitle);
|
||||
|
||||
if (confirm(confirmationMessage)) {
|
||||
// Disable button and show loading state
|
||||
deleteButton.disabled = true;
|
||||
deleteButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>{% trans "Deleting..." %}';
|
||||
|
||||
// Submit the form programmatically
|
||||
deleteForm.submit();
|
||||
} else {
|
||||
// If the user cancels the dialog, ensure the button state is reset/validated
|
||||
validateForm();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
220
templates/user/settings.html
Normal file
220
templates/user/settings.html
Normal file
@ -0,0 +1,220 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static i18n crispy_forms_tags %}
|
||||
|
||||
{% block title %}{% trans "System Settings" %} - ATS{% endblock %}
|
||||
|
||||
{% block customCSS %}
|
||||
<style>
|
||||
/* KAAT-S UI Variables */
|
||||
:root {
|
||||
--kaauh-teal: #00636e;
|
||||
--kaauh-teal-dark: #004a53;
|
||||
--kaauh-border: #eaeff3;
|
||||
--kaauh-primary-text: #343a40;
|
||||
--kaauh-success: #28a745;
|
||||
--kaauh-info: #17a2b8;
|
||||
--kaauh-danger: #dc3545;
|
||||
--kaauh-warning: #ffc107;
|
||||
}
|
||||
|
||||
/* Primary Color Overrides */
|
||||
.text-primary-theme { color: var(--kaauh-teal) !important; }
|
||||
.bg-primary-theme { background-color: var(--kaauh-teal) !important; }
|
||||
|
||||
/* Main Container & Card Styling */
|
||||
.kaauh-card {
|
||||
border: 1px solid var(--kaauh-border);
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/* Button Styling */
|
||||
.btn-main-action {
|
||||
background-color: var(--kaauh-teal);
|
||||
border-color: var(--kaauh-teal);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.btn-main-action:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
.btn-outline-secondary {
|
||||
color: var(--kaauh-teal-dark);
|
||||
border-color: var(--kaauh-teal);
|
||||
}
|
||||
.btn-outline-secondary:hover {
|
||||
background-color: var(--kaauh-teal-dark);
|
||||
color: white;
|
||||
border-color: var(--kaauh-teal-dark);
|
||||
}
|
||||
|
||||
/* Tabs Styling */
|
||||
.nav-tabs {
|
||||
border-bottom: 1px solid var(--kaauh-border);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.nav-tabs .nav-link {
|
||||
color: var(--kaauh-primary-text);
|
||||
border: 1px solid transparent;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
.nav-tabs .nav-link.active {
|
||||
color: var(--kaauh-teal-dark);
|
||||
background-color: white;
|
||||
border-color: var(--kaauh-border) var(--kaauh-border) white var(--kaauh-border);
|
||||
border-top: 3px solid var(--kaauh-teal); /* Active tab indicator */
|
||||
}
|
||||
|
||||
/* Settings Group Styling */
|
||||
.settings-group {
|
||||
border-bottom: 1px solid var(--kaauh-border);
|
||||
padding-bottom: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.settings-group:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.setting-action-btn {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row my-4 mx-4">
|
||||
|
||||
<div class="col-md-4 mb-4">
|
||||
<a href="#integration-settings-page" class="text-decoration-none">
|
||||
<div class="kaauh-card shadow-sm p-4 h-100" style="border-left: 5px solid var(--kaauh-teal);">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-plug fa-3x text-primary-theme me-4"></i>
|
||||
<div>
|
||||
<h5 class="fw-bold mb-1" style="color: var(--kaauh-teal-dark);">
|
||||
{% trans "Integration Settings" %}
|
||||
</h5>
|
||||
<p class="text-muted small mb-0">
|
||||
{% trans "Connect and manage external services like Zoom, email providers, and third-party tools." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end mt-3">
|
||||
<span class="btn btn-sm btn-outline-secondary">
|
||||
{% trans "Go to Settings" %} <i class="fas fa-arrow-right ms-1"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4">
|
||||
<a href="{% url 'source_list' %}" class="text-decoration-none">
|
||||
<div class="kaauh-card shadow-sm p-4 h-100" style="border-left: 5px solid var(--kaauh-teal);">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-sitemap fa-3x text-primary-theme me-4"></i>
|
||||
<div>
|
||||
<h5 class="fw-bold mb-1" style="color: var(--kaauh-teal-dark);">
|
||||
{% trans "Source & Sync Settings" %}
|
||||
</h5>
|
||||
<p class="text-muted small mb-0">
|
||||
{% trans "Configure automated syncs with job boards and external talent databases (CRM)." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end mt-3">
|
||||
<span class="btn btn-sm btn-outline-secondary">
|
||||
{% trans "Go to Settings" %} <i class="fas fa-arrow-right ms-1"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4">
|
||||
<a href="{% url 'admin_settings' %}" class="text-decoration-none">
|
||||
<div class="kaauh-card shadow-sm p-4 h-100" style="border-left: 5px solid var(--kaauh-teal);">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-user-friends fa-3x text-primary-theme me-4"></i>
|
||||
<div>
|
||||
<h5 class="fw-bold mb-1" style="color: var(--kaauh-teal-dark);">
|
||||
{% trans "Staff & Access Settings" %}
|
||||
</h5>
|
||||
<p class="text-muted small mb-0">
|
||||
{% trans "Manage user accounts, define roles, and control system permissions." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end mt-3">
|
||||
<span class="btn btn-sm btn-outline-secondary">
|
||||
{% trans "Go to Settings" %} <i class="fas fa-arrow-right ms-1"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-4 mb-4">
|
||||
<a href="{% url "easy_logs" %}" class="text-decoration-none">
|
||||
<div class="kaauh-card shadow-sm p-4 h-100" style="border-left: 5px solid var(--kaauh-teal);">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fas fa-file-alt fa-3x text-primary-theme me-4"></i>
|
||||
<div>
|
||||
<h5 class="fw-bold mb-1" style="color: var(--kaauh-teal-dark);">
|
||||
{% trans "System Activity logs" %}
|
||||
</h5>
|
||||
<p class="text-muted small mb-0">
|
||||
{% trans "Check the complete activity of your system here." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end mt-3">
|
||||
<button class="btn btn-sm btn-outline-secondary">
|
||||
{% trans "Go to Logs" %}<i class="fas fa-arrow-right ms-1"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4 mb-4">
|
||||
<a href="#linkedin-connect-action" class="text-decoration-none">
|
||||
<div class="kaauh-card shadow-sm p-4 h-100" style="border-left: 5px solid var(--kaauh-teal);">
|
||||
<div class="d-flex align-items-center">
|
||||
<i class="fab fa-linkedin fa-3x text-primary-theme me-4"></i>
|
||||
<div>
|
||||
<h5 class="fw-bold mb-1" style="color: var(--kaauh-teal-dark);">
|
||||
{% trans "LinkedIn Integration" %}
|
||||
</h5>
|
||||
<p class="text-muted small mb-0">
|
||||
{% trans "Connect the ATS with your LinkedIn Recruiter account to enable profile sourcing." %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end mt-3">
|
||||
{% if not request.session.linkedin_authenticated %}
|
||||
|
||||
<a class="dropdown-item py-2 px-4 d-flex align-items-center text-decoration-none text-teal" href="{% url 'linkedin_login' %}">
|
||||
<i class="fab fa-linkedin me-3 text-primary fs-5"></i>
|
||||
<span>{% trans "Connect to LinkedIn" %}</span>
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<p class="text-primary">
|
||||
<i class="fab fa-linkedin me-2"></i>
|
||||
{% trans "LinkedIn Connected" %}
|
||||
</p>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user