encrpytion removed #82

Merged
ismail merged 6 commits from frontend into main 2025-12-09 13:02:07 +03:00
34 changed files with 1764 additions and 442 deletions

View File

@ -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": "{",
},
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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')),

View File

@ -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
)

View File

@ -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)

View File

@ -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"),

View File

@ -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

View File

@ -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']
}

View File

@ -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
View 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

View File

@ -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

View File

@ -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 %}

View File

@ -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>

View File

@ -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';

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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 %}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 }}

View File

@ -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">

View File

@ -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 %}

View 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 %}