Merge branch 'main' of http://10.10.1.136:3000/marwan/kaauh_ats into frontend
This commit is contained in:
commit
b4b56d8a9d
6
.env
6
.env
@ -1,3 +1,3 @@
|
|||||||
DB_NAME=haikal_db
|
DB_NAME=norahuniversity
|
||||||
DB_USER=faheed
|
DB_USER=norahuniversity
|
||||||
DB_PASSWORD=Faheed@215
|
DB_PASSWORD=norahuniversity
|
||||||
@ -487,3 +487,6 @@ MESSAGE_TAGS = {
|
|||||||
|
|
||||||
# Custom User Model
|
# Custom User Model
|
||||||
AUTH_USER_MODEL = "recruitment.CustomUser"
|
AUTH_USER_MODEL = "recruitment.CustomUser"
|
||||||
|
|
||||||
|
|
||||||
|
ZOOM_WEBHOOK_API_KEY = "2GNDC5Rvyw9AHoGikHXsQB"
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -55,7 +55,7 @@ class SourceForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Source
|
model = Source
|
||||||
fields = ["name", "source_type", "description", "ip_address", "is_active"]
|
fields = ["name", "source_type", "description", "ip_address","trusted_ips", "is_active"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"name": forms.TextInput(
|
"name": forms.TextInput(
|
||||||
attrs={
|
attrs={
|
||||||
@ -81,6 +81,9 @@ class SourceForm(forms.ModelForm):
|
|||||||
"ip_address": forms.TextInput(
|
"ip_address": forms.TextInput(
|
||||||
attrs={"class": "form-control", "placeholder": "192.168.1.100"}
|
attrs={"class": "form-control", "placeholder": "192.168.1.100"}
|
||||||
),
|
),
|
||||||
|
"trusted_ips":forms.TextInput(
|
||||||
|
attrs={"class": "form-control", "placeholder": "192.168.1.100","required": False}
|
||||||
|
),
|
||||||
"is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
"is_active": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2228,7 +2231,7 @@ class CandidateSignupForm(forms.ModelForm):
|
|||||||
'last_name': forms.TextInput(attrs={'class': 'form-control'}),
|
'last_name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
'email': forms.EmailInput(attrs={'class': 'form-control'}),
|
||||||
'phone': forms.TextInput(attrs={'class': 'form-control'}),
|
'phone': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
'gpa': forms.TextInput(attrs={'class': 'form-control'}),
|
# 'gpa': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
"nationality": forms.Select(attrs={'class': 'form-control select2'}),
|
"nationality": forms.Select(attrs={'class': 'form-control select2'}),
|
||||||
'date_of_birth': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
'date_of_birth': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||||
'gender': forms.Select(attrs={'class': 'form-control'}),
|
'gender': forms.Select(attrs={'class': 'form-control'}),
|
||||||
|
|||||||
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 5.2.6 on 2025-11-18 10:24
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('recruitment', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='jobposting',
|
||||||
|
name='job_type',
|
||||||
|
field=models.CharField(choices=[('Full-time', 'Full-time'), ('Part-time', 'Part-time'), ('Contract', 'Contract'), ('Internship', 'Internship'), ('Faculty', 'Faculty'), ('Temporary', 'Temporary')], default='Full-time', max_length=20),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='jobposting',
|
||||||
|
name='workplace_type',
|
||||||
|
field=models.CharField(choices=[('On-site', 'On-site'), ('Remote', 'Remote'), ('Hybrid', 'Hybrid')], default='On-site', max_length=20),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='scheduledinterview',
|
||||||
|
name='interview_location',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_interview', to='recruitment.interviewlocation', verbose_name='Meeting/Location Details'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Binary file not shown.
@ -99,9 +99,9 @@ class JobPosting(Base):
|
|||||||
# Core Fields
|
# Core Fields
|
||||||
title = models.CharField(max_length=200)
|
title = models.CharField(max_length=200)
|
||||||
department = models.CharField(max_length=100, blank=True)
|
department = models.CharField(max_length=100, blank=True)
|
||||||
job_type = models.CharField(max_length=20, choices=JOB_TYPES, default="FULL_TIME")
|
job_type = models.CharField(max_length=20, choices=JOB_TYPES, default="Full-time")
|
||||||
workplace_type = models.CharField(
|
workplace_type = models.CharField(
|
||||||
max_length=20, choices=WORKPLACE_TYPES, default="ON_SITE"
|
max_length=20, choices=WORKPLACE_TYPES, default="On-site"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Location
|
# Location
|
||||||
|
|||||||
@ -18,7 +18,9 @@ from .models import (
|
|||||||
Notification,
|
Notification,
|
||||||
HiringAgency,
|
HiringAgency,
|
||||||
Person,
|
Person,
|
||||||
|
Source,
|
||||||
)
|
)
|
||||||
|
from .forms import generate_api_key, generate_api_secret
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -29,11 +31,10 @@ User = get_user_model()
|
|||||||
@receiver(post_save, sender=JobPosting)
|
@receiver(post_save, sender=JobPosting)
|
||||||
def format_job(sender, instance, created, **kwargs):
|
def format_job(sender, instance, created, **kwargs):
|
||||||
if created or not instance.ai_parsed:
|
if created or not instance.ai_parsed:
|
||||||
try:
|
form = getattr(instance, "form_template", None)
|
||||||
form_template = instance.form_template
|
if not form:
|
||||||
except FormTemplate.DoesNotExist:
|
|
||||||
FormTemplate.objects.get_or_create(
|
FormTemplate.objects.get_or_create(
|
||||||
job=instance, is_active=False, name=instance.title
|
job=instance, is_active=True, name=instance.title
|
||||||
)
|
)
|
||||||
async_task(
|
async_task(
|
||||||
"recruitment.tasks.format_job_description",
|
"recruitment.tasks.format_job_description",
|
||||||
@ -469,3 +470,27 @@ def person_created(sender, instance, created, **kwargs):
|
|||||||
)
|
)
|
||||||
instance.user = user
|
instance.user = user
|
||||||
instance.save()
|
instance.save()
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Source)
|
||||||
|
def source_created(sender, instance, created, **kwargs):
|
||||||
|
"""
|
||||||
|
Automatically generate API key and API secret when a new Source is created.
|
||||||
|
"""
|
||||||
|
if created:
|
||||||
|
# Only generate keys if they don't already exist
|
||||||
|
if not instance.api_key and not instance.api_secret:
|
||||||
|
logger.info(f"Generating API keys for new Source: {instance.pk} - {instance.name}")
|
||||||
|
|
||||||
|
# Generate API key and secret using existing secure functions
|
||||||
|
api_key = generate_api_key()
|
||||||
|
api_secret = generate_api_secret()
|
||||||
|
|
||||||
|
# Update the source with generated keys
|
||||||
|
instance.api_key = api_key
|
||||||
|
instance.api_secret = api_secret
|
||||||
|
instance.save(update_fields=['api_key', 'api_secret'])
|
||||||
|
|
||||||
|
logger.info(f"API keys generated successfully for Source: {instance.name} (Key: {api_key[:8]}...)")
|
||||||
|
else:
|
||||||
|
logger.info(f"Source {instance.name} already has API keys, skipping generation")
|
||||||
|
|||||||
@ -28,7 +28,7 @@ logger = logging.getLogger(__name__)
|
|||||||
OPENROUTER_API_KEY ='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'
|
OPENROUTER_API_KEY ='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'
|
||||||
# OPENROUTER_MODEL = 'qwen/qwen-2.5-72b-instruct:free'
|
# OPENROUTER_MODEL = 'qwen/qwen-2.5-72b-instruct:free'
|
||||||
|
|
||||||
OPENROUTER_MODEL = 'openai/gpt-oss-20b'
|
OPENROUTER_MODEL = 'qwen/qwen-2.5-72b-instruct'
|
||||||
# OPENROUTER_MODEL = 'openai/gpt-oss-20b'
|
# OPENROUTER_MODEL = 'openai/gpt-oss-20b'
|
||||||
# OPENROUTER_MODEL = 'mistralai/mistral-small-3.2-24b-instruct:free'
|
# OPENROUTER_MODEL = 'mistralai/mistral-small-3.2-24b-instruct:free'
|
||||||
|
|
||||||
@ -506,6 +506,7 @@ def handle_zoom_webhook_event(payload):
|
|||||||
Background task to process a Zoom webhook event and update the local ZoomMeeting status.
|
Background task to process a Zoom webhook event and update the local ZoomMeeting status.
|
||||||
It handles: created, updated, started, ended, and deleted events.
|
It handles: created, updated, started, ended, and deleted events.
|
||||||
"""
|
"""
|
||||||
|
print(payload)
|
||||||
event_type = payload.get('event')
|
event_type = payload.get('event')
|
||||||
object_data = payload['payload']['object']
|
object_data = payload['payload']['object']
|
||||||
|
|
||||||
@ -534,7 +535,9 @@ def handle_zoom_webhook_event(payload):
|
|||||||
# elif event_type == 'meeting.updated':
|
# elif event_type == 'meeting.updated':
|
||||||
# Only update time fields if they are in the payload
|
# Only update time fields if they are in the payload
|
||||||
print(object_data)
|
print(object_data)
|
||||||
meeting_instance.start_time = object_data.get('start_time', meeting_instance.start_time)
|
meeting_start_time = object_data.get('start_time', meeting_instance.start_time)
|
||||||
|
if meeting_start_time:
|
||||||
|
meeting_instance.start_time = datetime.fromisoformat(meeting_start_time)
|
||||||
meeting_instance.duration = object_data.get('duration', meeting_instance.duration)
|
meeting_instance.duration = object_data.get('duration', meeting_instance.duration)
|
||||||
meeting_instance.timezone = object_data.get('timezone', meeting_instance.timezone)
|
meeting_instance.timezone = object_data.get('timezone', meeting_instance.timezone)
|
||||||
|
|
||||||
|
|||||||
@ -1336,6 +1336,7 @@ def application_submit(request, template_slug):
|
|||||||
# email = submission.responses.get(field__label="Email Address")
|
# email = submission.responses.get(field__label="Email Address")
|
||||||
# phone = submission.responses.get(field__label="Phone Number")
|
# phone = submission.responses.get(field__label="Phone Number")
|
||||||
# address = submission.responses.get(field__label="Address")
|
# address = submission.responses.get(field__label="Address")
|
||||||
|
gpa = submission.responses.get(field__label="GPA")
|
||||||
|
|
||||||
resume = submission.responses.get(field__label="Resume Upload")
|
resume = submission.responses.get(field__label="Resume Upload")
|
||||||
|
|
||||||
@ -1346,6 +1347,8 @@ def application_submit(request, template_slug):
|
|||||||
submission.save()
|
submission.save()
|
||||||
# time=timezone.now()
|
# time=timezone.now()
|
||||||
person = request.user.person_profile
|
person = request.user.person_profile
|
||||||
|
person.gpa = gpa.value if gpa else None
|
||||||
|
person.save()
|
||||||
Application.objects.create(
|
Application.objects.create(
|
||||||
person = person,
|
person = person,
|
||||||
resume=resume.get_file if resume.is_file else None,
|
resume=resume.get_file if resume.is_file else None,
|
||||||
@ -1806,7 +1809,7 @@ def candidate_screening_view(request, slug):
|
|||||||
min_experience_str = request.GET.get("min_experience")
|
min_experience_str = request.GET.get("min_experience")
|
||||||
screening_rating = request.GET.get("screening_rating")
|
screening_rating = request.GET.get("screening_rating")
|
||||||
tier1_count_str = request.GET.get("tier1_count")
|
tier1_count_str = request.GET.get("tier1_count")
|
||||||
gpa = request.GET.get("gpa")
|
gpa = request.GET.get("GPA")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Check if the string value exists and is not an empty string before conversion
|
# Check if the string value exists and is not an empty string before conversion
|
||||||
@ -1854,8 +1857,9 @@ def candidate_screening_view(request, slug):
|
|||||||
)
|
)
|
||||||
if gpa:
|
if gpa:
|
||||||
candidates = candidates.filter(
|
candidates = candidates.filter(
|
||||||
person__gpa = gpa
|
person__gpa__gt= gpa
|
||||||
)
|
)
|
||||||
|
print(candidates)
|
||||||
|
|
||||||
if tier1_count > 0:
|
if tier1_count > 0:
|
||||||
candidates = candidates[:tier1_count]
|
candidates = candidates[:tier1_count]
|
||||||
@ -3018,7 +3022,6 @@ def is_superuser_check(user):
|
|||||||
def create_staff_user(request):
|
def create_staff_user(request):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = StaffUserCreationForm(request.POST)
|
form = StaffUserCreationForm(request.POST)
|
||||||
print(form)
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
messages.success(
|
messages.success(
|
||||||
@ -3034,7 +3037,7 @@ def create_staff_user(request):
|
|||||||
|
|
||||||
@staff_user_required
|
@staff_user_required
|
||||||
def admin_settings(request):
|
def admin_settings(request):
|
||||||
staffs = User.objects.filter(is_superuser=False)
|
staffs = User.objects.filter(user_type="staff",is_superuser=False)
|
||||||
form = ToggleAccountForm()
|
form = ToggleAccountForm()
|
||||||
context = {"staffs": staffs, "form": form}
|
context = {"staffs": staffs, "form": form}
|
||||||
return render(request, "user/admin_settings.html", context)
|
return render(request, "user/admin_settings.html", context)
|
||||||
@ -3097,12 +3100,11 @@ def account_toggle_status(request, pk):
|
|||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@staff_user_required
|
|
||||||
def zoom_webhook_view(request):
|
def zoom_webhook_view(request):
|
||||||
print(request.headers)
|
api_key = request.headers.get("X-Zoom-API-KEY")
|
||||||
print(settings.ZOOM_WEBHOOK_API_KEY)
|
if api_key != settings.ZOOM_WEBHOOK_API_KEY:
|
||||||
# if api_key != settings.ZOOM_WEBHOOK_API_KEY:
|
return HttpResponse(status=405)
|
||||||
# return HttpResponse(status=405)
|
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
try:
|
try:
|
||||||
payload = json.loads(request.body)
|
payload = json.loads(request.body)
|
||||||
@ -5497,7 +5499,7 @@ def candidate_signup(request, slug):
|
|||||||
gender = form.cleaned_data["gender"]
|
gender = form.cleaned_data["gender"]
|
||||||
nationality = form.cleaned_data["nationality"]
|
nationality = form.cleaned_data["nationality"]
|
||||||
address = form.cleaned_data["address"]
|
address = form.cleaned_data["address"]
|
||||||
gpa = form.cleaned_data["gpa"]
|
# gpa = form.cleaned_data["gpa"]
|
||||||
password = form.cleaned_data["password"]
|
password = form.cleaned_data["password"]
|
||||||
|
|
||||||
user = User.objects.create_user(
|
user = User.objects.create_user(
|
||||||
@ -5512,7 +5514,7 @@ def candidate_signup(request, slug):
|
|||||||
phone=phone,
|
phone=phone,
|
||||||
gender=gender,
|
gender=gender,
|
||||||
nationality=nationality,
|
nationality=nationality,
|
||||||
gpa=gpa,
|
# gpa=gpa,
|
||||||
address=address,
|
address=address,
|
||||||
user = user
|
user = user
|
||||||
)
|
)
|
||||||
|
|||||||
@ -204,26 +204,27 @@ def generate_api_keys_view(request, pk):
|
|||||||
source.save()
|
source.save()
|
||||||
|
|
||||||
# Log the key regeneration
|
# Log the key regeneration
|
||||||
IntegrationLog.objects.create(
|
# IntegrationLog.objects.create(
|
||||||
source=source,
|
# source=source,
|
||||||
action=IntegrationLog.ActionChoices.CREATE,
|
# action=IntegrationLog.ActionChoices.CREATE,
|
||||||
endpoint=f'/api/sources/{source.pk}/generate-keys/',
|
# endpoint=f'/api/sources/{source.pk}/generate-keys/',
|
||||||
method='POST',
|
# method='POST',
|
||||||
request_data={
|
# request_data={
|
||||||
'name': source.name,
|
# 'name': source.name,
|
||||||
'old_api_key': old_api_key[:8] + '...' if old_api_key else None,
|
# 'old_api_key': old_api_key[:8] + '...' if old_api_key else None,
|
||||||
'new_api_key': new_api_key[:8] + '...'
|
# 'new_api_key': new_api_key[:8] + '...'
|
||||||
},
|
# },
|
||||||
ip_address=request.META.get('REMOTE_ADDR'),
|
# ip_address=request.META.get('REMOTE_ADDR'),
|
||||||
user_agent=request.META.get('HTTP_USER_AGENT', '')
|
# user_agent=request.META.get('HTTP_USER_AGENT', '')
|
||||||
)
|
# )
|
||||||
|
|
||||||
return JsonResponse({
|
return redirect('source_detail', pk=source.pk)
|
||||||
'success': True,
|
# return JsonResponse({
|
||||||
'api_key': new_api_key,
|
# 'success': True,
|
||||||
'api_secret': new_api_secret,
|
# 'api_key': new_api_key,
|
||||||
'message': 'API keys regenerated successfully'
|
# 'api_secret': new_api_secret,
|
||||||
})
|
# 'message': 'API keys regenerated successfully'
|
||||||
|
# })
|
||||||
|
|
||||||
return JsonResponse({'error': 'Invalid request method'}, status=405)
|
return JsonResponse({'error': 'Invalid request method'}, status=405)
|
||||||
|
|
||||||
@ -244,27 +245,28 @@ def toggle_source_status_view(request, pk):
|
|||||||
source.save()
|
source.save()
|
||||||
|
|
||||||
# Log the status change
|
# Log the status change
|
||||||
IntegrationLog.objects.create(
|
# IntegrationLog.objects.create(
|
||||||
source=source,
|
# source=source,
|
||||||
action=IntegrationLog.ActionChoices.SYNC,
|
# action=IntegrationLog.ActionChoices.SYNC,
|
||||||
endpoint=f'/api/sources/{source.pk}/toggle-status/',
|
# endpoint=f'/api/sources/{source.pk}/toggle-status/',
|
||||||
method='POST',
|
# method='POST',
|
||||||
request_data={
|
# request_data={
|
||||||
'name': source.name,
|
# 'name': source.name,
|
||||||
'old_status': old_status,
|
# 'old_status': old_status,
|
||||||
'new_status': source.is_active
|
# 'new_status': source.is_active
|
||||||
},
|
# },
|
||||||
ip_address=request.META.get('REMOTE_ADDR'),
|
# ip_address=request.META.get('REMOTE_ADDR'),
|
||||||
user_agent=request.META.get('HTTP_USER_AGENT', '')
|
# user_agent=request.META.get('HTTP_USER_AGENT', '')
|
||||||
)
|
# )
|
||||||
|
|
||||||
status_text = 'activated' if source.is_active else 'deactivated'
|
status_text = 'activated' if source.is_active else 'deactivated'
|
||||||
|
|
||||||
return JsonResponse({
|
return redirect('source_detail', pk=source.pk)
|
||||||
'success': True,
|
# return JsonResponse({
|
||||||
'is_active': source.is_active,
|
# 'success': True,
|
||||||
'message': f'Source "{source.name}" {status_text} successfully'
|
# 'is_active': source.is_active,
|
||||||
})
|
# 'message': f'Source "{source.name}" {status_text} successfully'
|
||||||
|
# })
|
||||||
|
|
||||||
def copy_to_clipboard_view(request):
|
def copy_to_clipboard_view(request):
|
||||||
"""HTMX endpoint to copy text to clipboard"""
|
"""HTMX endpoint to copy text to clipboard"""
|
||||||
|
|||||||
@ -32,8 +32,8 @@
|
|||||||
--gray-text: #6c757d;
|
--gray-text: #6c757d;
|
||||||
--kaauh-border: #d0d7de; /* Cleaner border color */
|
--kaauh-border: #d0d7de; /* Cleaner border color */
|
||||||
--kaauh-shadow: 0 15px 35px rgba(0, 0, 0, 0.1); /* Deeper shadow for premium look */
|
--kaauh-shadow: 0 15px 35px rgba(0, 0, 0, 0.1); /* Deeper shadow for premium look */
|
||||||
--kaauh-dark-bg: #0d0d0d;
|
--kaauh-dark-bg: #0d0d0d;
|
||||||
--kaauh-dark-contrast: #1c1c1c;
|
--kaauh-dark-contrast: #1c1c1c;
|
||||||
|
|
||||||
/* CALCULATED STICKY HEIGHTS (As provided in base) */
|
/* CALCULATED STICKY HEIGHTS (As provided in base) */
|
||||||
--navbar-height: 56px;
|
--navbar-height: 56px;
|
||||||
@ -43,17 +43,145 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #f0f0f5;
|
background-color: #f0f0f5;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-primary-theme { color: var(--kaauh-teal) !important; }
|
.text-primary-theme { color: var(--kaauh-teal) !important; }
|
||||||
.text-primary-theme-hover:hover { color: var(--kaauh-teal-dark) !important; }
|
.text-primary-theme-hover:hover { color: var(--kaauh-teal-dark) !important; }
|
||||||
|
|
||||||
|
/* Language Dropdown Styles */
|
||||||
|
.language-toggle-btn {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid var(--kaauh-border);
|
||||||
|
color: var(--kaauh-teal);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
min-width: 120px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-toggle-btn:hover {
|
||||||
|
background-color: var(--kaauh-teal-light);
|
||||||
|
border-color: var(--kaauh-teal);
|
||||||
|
color: var(--kaauh-teal-dark);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 99, 110, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-toggle-btn:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||||
|
border-color: var(--kaauh-teal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-toggle-btn::after {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
border: 1px solid var(--kaauh-border);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 0.5rem;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .dropdown-item {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
width: 100%;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .dropdown-item:hover {
|
||||||
|
background-color: var(--kaauh-teal);
|
||||||
|
color: white;
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .dropdown-item:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(0, 99, 110, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .dropdown-item.active {
|
||||||
|
background-color: var(--kaauh-teal);
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 99, 110, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .dropdown-item.active:hover {
|
||||||
|
background-color: var(--kaauh-teal-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag-emoji {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
line-height: 1;
|
||||||
|
min-width: 24px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-text {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RTL Support for Language Dropdown */
|
||||||
|
html[dir="rtl"] .language-toggle-btn {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .dropdown-menu .dropdown-item {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[dir="rtl"] .dropdown-menu .dropdown-item:hover {
|
||||||
|
transform: translateX(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile Responsiveness */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.language-toggle-btn {
|
||||||
|
min-width: 100px;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu .dropdown-item {
|
||||||
|
padding: 0.6rem 0.8rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flag-emoji {
|
||||||
|
font-size: 1rem;
|
||||||
|
min-width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.bg-kaauh-teal {
|
.bg-kaauh-teal {
|
||||||
background-color: #00636e;
|
background-color: #00636e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-main-action {
|
.btn-main-action {
|
||||||
background-color: var(--kaauh-teal);
|
background-color: var(--kaauh-teal);
|
||||||
color: white;
|
color: white;
|
||||||
@ -67,7 +195,7 @@
|
|||||||
background-color: var(--kaauh-teal-dark);
|
background-color: var(--kaauh-teal-dark);
|
||||||
color: white;
|
color: white;
|
||||||
transform: translateY(-2px); /* More pronounced lift */
|
transform: translateY(-2px); /* More pronounced lift */
|
||||||
box-shadow: 0 10px 20px rgba(0, 99, 110, 0.5);
|
box-shadow: 0 10px 20px rgba(0, 99, 110, 0.5);
|
||||||
}
|
}
|
||||||
/* ---------------------------------------------------------------------- */
|
/* ---------------------------------------------------------------------- */
|
||||||
/* 1. DARK HERO STYLING (High Contrast) */
|
/* 1. DARK HERO STYLING (High Contrast) */
|
||||||
@ -75,16 +203,16 @@
|
|||||||
.hero-section {
|
.hero-section {
|
||||||
background: linear-gradient(135deg, var(--kaauh-dark-contrast) 0%, var(--kaauh-dark-bg) 100%);
|
background: linear-gradient(135deg, var(--kaauh-dark-contrast) 0%, var(--kaauh-dark-bg) 100%);
|
||||||
padding: 4rem 0; /* Reduced from 8rem to 4rem */
|
padding: 4rem 0; /* Reduced from 8rem to 4rem */
|
||||||
margin-top: -1px;
|
margin-top: -1px;
|
||||||
color: white;
|
color: white;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.hero-title {
|
.hero-title {
|
||||||
font-size: 2.5rem; /* Reduced from 3.5rem to 2.5rem */
|
font-size: 2.5rem; /* Reduced from 3.5rem to 2.5rem */
|
||||||
font-weight: 800; /* Extra bold */
|
font-weight: 800; /* Extra bold */
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
letter-spacing: -0.05em;
|
letter-spacing: -0.05em;
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
}
|
}
|
||||||
.hero-section .lead {
|
.hero-section .lead {
|
||||||
@ -104,7 +232,7 @@
|
|||||||
padding: 10rem 0;
|
padding: 10rem 0;
|
||||||
}
|
}
|
||||||
.hero-title {
|
.hero-title {
|
||||||
font-size: 5.5rem;
|
font-size: 5.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,20 +281,20 @@
|
|||||||
background-color: #f0f0f5; /* Separates the job list from the white path section */
|
background-color: #f0f0f5; /* Separates the job list from the white path section */
|
||||||
padding-top: 3rem;
|
padding-top: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.job-listing-card {
|
.job-listing-card {
|
||||||
border: 1px solid var(--kaauh-border);
|
border: 1px solid var(--kaauh-border);
|
||||||
border-left: 6px solid var(--kaauh-teal);
|
border-left: 6px solid var(--kaauh-teal);
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
padding: 2rem !important;
|
padding: 2rem !important;
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); /* Lighter default shadow */
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); /* Lighter default shadow */
|
||||||
}
|
}
|
||||||
.job-listing-card:hover {
|
.job-listing-card:hover {
|
||||||
transform: translateY(-3px); /* Increased lift */
|
transform: translateY(-3px); /* Increased lift */
|
||||||
box-shadow: 0 12px 25px rgba(0, 99, 110, 0.15); /* Stronger hover shadow */
|
box-shadow: 0 12px 25px rgba(0, 99, 110, 0.15); /* Stronger hover shadow */
|
||||||
background-color: var(--kaauh-teal-light);
|
background-color: var(--kaauh-teal-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card.sticky-top-filters {
|
.card.sticky-top-filters {
|
||||||
box-shadow: var(--kaauh-shadow); /* Uses the deeper card shadow */
|
box-shadow: var(--kaauh-shadow); /* Uses the deeper card shadow */
|
||||||
}
|
}
|
||||||
@ -215,7 +343,8 @@
|
|||||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||||
<button name="language" value="en" class="dropdown-item {% if LANGUAGE_CODE == 'en' %}active bg-light-subtle{% endif %}" type="submit">
|
<button name="language" value="en" class="dropdown-item {% if LANGUAGE_CODE == 'en' %}active bg-light-subtle{% endif %}" type="submit">
|
||||||
<span class="me-2">🇺🇸</span> English
|
<span class="flag-emoji">🇺🇸</span>
|
||||||
|
<span class="language-text">English</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
@ -224,7 +353,8 @@
|
|||||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||||
<button name="language" value="ar" class="dropdown-item {% if LANGUAGE_CODE == 'ar' %}active bg-light-subtle{% endif %}" type="submit">
|
<button name="language" value="ar" class="dropdown-item {% if LANGUAGE_CODE == 'ar' %}active bg-light-subtle{% endif %}" type="submit">
|
||||||
<span class="me-2">🇸🇦</span> العربية (Arabic)
|
<span class="flag-emoji">🇸🇦</span>
|
||||||
|
<span class="language-text">العربية (Arabic)</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</li>
|
</li>
|
||||||
@ -239,7 +369,7 @@
|
|||||||
<div class="container message-container mt-3">
|
<div class="container message-container mt-3">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{# Use responsive columns matching the main content block for alignment #}
|
{# Use responsive columns matching the main content block for alignment #}
|
||||||
<div class="col-lg-12 order-lg-1 col-12 mx-auto">
|
<div class="col-lg-12 order-lg-1 col-12 mx-auto">
|
||||||
{% for message in messages %}
|
{% for message in messages %}
|
||||||
<div class="alert alert-{{ message.tags|default:'info' }} alert-dismissible fade show" role="alert">
|
<div class="alert alert-{{ message.tags|default:'info' }} alert-dismissible fade show" role="alert">
|
||||||
<i class="fas fa-check-circle me-2"></i> {{ message }}
|
<i class="fas fa-check-circle me-2"></i> {{ message }}
|
||||||
@ -254,20 +384,20 @@
|
|||||||
{# ================================================= #}
|
{# ================================================= #}
|
||||||
{# DJANGO MESSAGE BLOCK - Placed directly below the main navbar #}
|
{# DJANGO MESSAGE BLOCK - Placed directly below the main navbar #}
|
||||||
{# ================================================= #}
|
{# ================================================= #}
|
||||||
|
|
||||||
{# ================================================= #}
|
{# ================================================= #}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
||||||
{% block customJS %}
|
{% block customJS %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -250,7 +250,7 @@
|
|||||||
<a class="nav-link {% if request.resolver_match.url_name == 'person_list' %}active{% endif %}" href="{% url 'person_list' %}">
|
<a class="nav-link {% if request.resolver_match.url_name == 'person_list' %}active{% endif %}" href="{% url 'person_list' %}">
|
||||||
<span class="d-flex align-items-center gap-2">
|
<span class="d-flex align-items-center gap-2">
|
||||||
{% include "icons/users.html" %}
|
{% include "icons/users.html" %}
|
||||||
{% trans "Person" %}
|
{% trans "Applicant" %}
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -272,7 +272,7 @@
|
|||||||
<th scope="col" rowspan="2">{% trans "Actions" %}</th>
|
<th scope="col" rowspan="2">{% trans "Actions" %}</th>
|
||||||
<th scope="col" rowspan="2" class="text-center">{% trans "Manage Forms" %}</th>
|
<th scope="col" rowspan="2" class="text-center">{% trans "Manage Forms" %}</th>
|
||||||
|
|
||||||
<th scope="col" colspan="5" class="candidate-management-header-title">
|
<th scope="col" colspan="6" class="candidate-management-header-title">
|
||||||
{% trans "Applicants Metrics" %}
|
{% trans "Applicants Metrics" %}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -282,10 +282,11 @@
|
|||||||
<th style="width: calc(50% / 7);">{% trans "Screened" %}</th>
|
<th style="width: calc(50% / 7);">{% trans "Screened" %}</th>
|
||||||
<th style="width: calc(50% / 7 * 2);">{% trans "Exam" %}</th>
|
<th style="width: calc(50% / 7 * 2);">{% trans "Exam" %}</th>
|
||||||
<th style="width: calc(50% / 7 * 2);">{% trans "Interview" %}</th>
|
<th style="width: calc(50% / 7 * 2);">{% trans "Interview" %}</th>
|
||||||
|
<th style="width: calc(50% / 7 * 2);">{% trans "Documets Review" %}</th>
|
||||||
<th style="width: calc(50% / 7);">{% trans "Offer" %}</th>
|
<th style="width: calc(50% / 7);">{% trans "Offer" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for job in jobs %}
|
{% for job in jobs %}
|
||||||
<tr>
|
<tr>
|
||||||
@ -311,7 +312,7 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
{% if job.form_template %}
|
{% if job.form_template %}
|
||||||
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Preview' %}">
|
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-outline-secondary {% if job.status != 'ACTIVE' %}disabled{% endif %}" title="{% trans 'Preview' %}">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'form_builder' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
<a href="{% url 'form_builder' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'Edit' %}">
|
||||||
@ -329,6 +330,7 @@
|
|||||||
<td class="candidate-data-cell text-info"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-info">{% if job.screening_candidates.count %}{{ job.screening_candidates.count }}{% else %}-{% endif %}</a></td>
|
<td class="candidate-data-cell text-info"><a href="{% url 'candidate_screening_view' job.slug %}" class="text-info">{% if job.screening_candidates.count %}{{ job.screening_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||||
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_exam_view' job.slug %}" class="text-success">{% if job.exam_candidates.count %}{{ job.exam_candidates.count }}{% else %}-{% endif %}</a></td>
|
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_exam_view' job.slug %}" class="text-success">{% if job.exam_candidates.count %}{{ job.exam_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||||
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_interview_view' job.slug %}" class="text-success">{% if job.interview_candidates.count %}{{ job.interview_candidates.count }}{% else %}-{% endif %}</a></td>
|
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_interview_view' job.slug %}" class="text-success">{% if job.interview_candidates.count %}{{ job.interview_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||||
|
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_document_review_view' job.slug %}" class="text-success">{% if job.document_review_candidates.count %}{{ job.document_review_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||||
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_offer_view' job.slug %}" class="text-success">{% if job.offer_candidates.count %}{{ job.offer_candidates.count }}{% else %}-{% endif %}</a></td>
|
<td class="candidate-data-cell text-success"><a href="{% url 'candidate_offer_view' job.slug %}" class="text-success">{% if job.offer_candidates.count %}{{ job.offer_candidates.count }}{% else %}-{% endif %}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -113,7 +113,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% comment %} CONNECTOR 1 -> 2 {% endcomment %}
|
{% comment %} CONNECTOR 1 -> 2 {% endcomment %}
|
||||||
<div class="stage-connector {% if current_stage == 'Exam' or current_stage == 'Interview' or current_stage == 'Offer' %}completed{% endif %}"></div>
|
<div class="stage-connector {% if current_stage == 'Exam' or current_stage == 'Interview' or current_stage == 'Document Review' or current_stage == 'Offer' or current_stage == 'Hired' %}completed{% endif %}"></div>
|
||||||
|
|
||||||
{% comment %} STAGE 2: Exam {% endcomment %}
|
{% comment %} STAGE 2: Exam {% endcomment %}
|
||||||
<a href="{% url 'candidate_exam_view' job.slug %}"
|
<a href="{% url 'candidate_exam_view' job.slug %}"
|
||||||
@ -127,7 +127,7 @@
|
|||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% comment %} CONNECTOR 2 -> 3 {% endcomment %}
|
{% comment %} CONNECTOR 2 -> 3 {% endcomment %}
|
||||||
<div class="stage-connector {% if current_stage == 'Interview' or current_stage == 'Offer' %}completed{% endif %}"></div>
|
<div class="stage-connector {% if current_stage == 'Interview' or current_stage == 'Document Review' or current_stage == 'Offer' or current_stage == 'Hired' %}completed{% endif %}"></div>
|
||||||
|
|
||||||
{% comment %} STAGE 3: Interview {% endcomment %}
|
{% comment %} STAGE 3: Interview {% endcomment %}
|
||||||
<a href="{% url 'candidate_interview_view' job.slug %}"
|
<a href="{% url 'candidate_interview_view' job.slug %}"
|
||||||
|
|||||||
@ -152,10 +152,10 @@
|
|||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||||
<i class="fas fa-user-friends me-2"></i> {% trans "People Directory" %}
|
<i class="fas fa-user-friends me-2"></i> {% trans "Applicants List" %}
|
||||||
</h1>
|
</h1>
|
||||||
<a href="{% url 'person_create' %}" class="btn btn-main-action">
|
<a href="{% url 'person_create' %}" class="btn btn-main-action">
|
||||||
<i class="fas fa-plus me-1"></i> {% trans "Add New Person" %}
|
<i class="fas fa-plus me-1"></i> {% trans "Add New" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -168,7 +168,7 @@
|
|||||||
<form method="get" action="" class="w-100">
|
<form method="get" action="" class="w-100">
|
||||||
<div class="input-group input-group-lg">
|
<div class="input-group input-group-lg">
|
||||||
<input type="text" name="q" class="form-control" id="search"
|
<input type="text" name="q" class="form-control" id="search"
|
||||||
placeholder="{% trans 'Search people...' %}"
|
placeholder="{% trans 'Search applicant...' %}"
|
||||||
value="{{ request.GET.q }}">
|
value="{{ request.GET.q }}">
|
||||||
<button class="btn btn-main-action" type="submit">
|
<button class="btn btn-main-action" type="submit">
|
||||||
<i class="fas fa-search"></i>
|
<i class="fas fa-search"></i>
|
||||||
|
|||||||
@ -89,6 +89,7 @@
|
|||||||
|
|
||||||
.status-applied { background: #e3f2fd; color: #1976d2; }
|
.status-applied { background: #e3f2fd; color: #1976d2; }
|
||||||
.status-screening { background: #fff3e0; color: #f57c00; }
|
.status-screening { background: #fff3e0; color: #f57c00; }
|
||||||
|
.status-document_review { background: #f3e5f5; color: #7b1fa2; }
|
||||||
.status-exam { background: #f3e5f5; color: #7b1fa2; }
|
.status-exam { background: #f3e5f5; color: #7b1fa2; }
|
||||||
.status-interview { background: #e8f5e8; color: #388e3c; }
|
.status-interview { background: #e8f5e8; color: #388e3c; }
|
||||||
.status-offer { background: #fff8e1; color: #f9a825; }
|
.status-offer { background: #fff8e1; color: #f9a825; }
|
||||||
@ -119,6 +120,9 @@
|
|||||||
background-color: #f3e5f5;
|
background-color: #f3e5f5;
|
||||||
border-color: #ce93d8;
|
border-color: #ce93d8;
|
||||||
}
|
}
|
||||||
|
.card-header{
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -155,7 +159,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 text-end">
|
<div class="col-md-4 text-end">
|
||||||
<span class="status-badge status-{{ application.stage|lower }}">
|
<span class="status-badge status-{{ application.stage }}">
|
||||||
{{ application.get_stage_display }}
|
{{ application.get_stage_display }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -172,19 +176,9 @@
|
|||||||
<div class="progress-label">{% trans "Applied" %}</div>
|
<div class="progress-label">{% trans "Applied" %}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Screening Stage - Show if current stage is Screening or beyond -->
|
|
||||||
{% if application.stage in 'Screening,Exam,Interview,Offer,Hired,Rejected' %}
|
|
||||||
<div class="progress-step {% if application.stage not in 'Applied,Screening' %}completed{% elif application.stage == 'Screening' %}active{% endif %}">
|
|
||||||
<div class="progress-icon">
|
|
||||||
<i class="fas fa-search"></i>
|
|
||||||
</div>
|
|
||||||
<div class="progress-label">{% trans "Screening" %}</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Exam Stage - Show if current stage is Exam or beyond -->
|
<!-- Exam Stage - Show if current stage is Exam or beyond -->
|
||||||
{% if application.stage in 'Exam,Interview,Offer,Hired,Rejected' %}
|
{% if application.stage in 'Exam,Interview,Document Review,Offer,Hired,Rejected' %}
|
||||||
<div class="progress-step {% if application.stage not in 'Applied,Screening,Exam' %}completed{% elif application.stage == 'Exam' %}active{% endif %}">
|
<div class="progress-step {% if application.stage not in 'Applied,Exam' %}completed{% elif application.stage == 'Exam' %}active{% endif %}">
|
||||||
<div class="progress-icon">
|
<div class="progress-icon">
|
||||||
<i class="fas fa-clipboard-check"></i>
|
<i class="fas fa-clipboard-check"></i>
|
||||||
</div>
|
</div>
|
||||||
@ -193,8 +187,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<!-- Interview Stage - Show if current stage is Interview or beyond -->
|
<!-- Interview Stage - Show if current stage is Interview or beyond -->
|
||||||
{% if application.stage in 'Interview,Offer,Hired,Rejected' %}
|
{% if application.stage in 'Interview,Document Review,Offer,Hired,Rejected' %}
|
||||||
<div class="progress-step {% if application.stage not in 'Applied,Screening,Exam,Interview' %}completed{% elif application.stage == 'Interview' %}active{% endif %}">
|
<div class="progress-step {% if application.stage not in 'Applied,Exam,Interview' %}completed{% elif application.stage == 'Interview' %}active{% endif %}">
|
||||||
<div class="progress-icon">
|
<div class="progress-icon">
|
||||||
<i class="fas fa-video"></i>
|
<i class="fas fa-video"></i>
|
||||||
</div>
|
</div>
|
||||||
@ -202,9 +196,19 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Document Review Stage - Show if current stage is Document Review or beyond -->
|
||||||
|
{% if application.stage in 'Document Review,Offer,Hired,Rejected' %}
|
||||||
|
<div class="progress-step {% if application.stage not in 'Applied,Exam,Interview,Document Review' %}completed{% elif application.stage == 'Document Review' %}active{% endif %}">
|
||||||
|
<div class="progress-icon">
|
||||||
|
<i class="fas fa-file-alt"></i>
|
||||||
|
</div>
|
||||||
|
<div class="progress-label">{% trans "Document Review" %}</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- Offer Stage - Show if current stage is Offer or beyond -->
|
<!-- Offer Stage - Show if current stage is Offer or beyond -->
|
||||||
{% if application.stage in 'Offer,Hired,Rejected' %}
|
{% if application.stage in 'Offer,Hired,Rejected' %}
|
||||||
<div class="progress-step {% if application.stage not in 'Applied,Screening,Exam,Interview,Offer' %}completed{% elif application.stage == 'Offer' %}active{% endif %}">
|
<div class="progress-step {% if application.stage not in 'Applied,Exam,Interview,Document Review,Offer' %}completed{% elif application.stage == 'Offer' %}active{% endif %}">
|
||||||
<div class="progress-icon">
|
<div class="progress-icon">
|
||||||
<i class="fas fa-handshake"></i>
|
<i class="fas fa-handshake"></i>
|
||||||
</div>
|
</div>
|
||||||
@ -525,6 +529,11 @@
|
|||||||
<i class="fas fa-search me-2"></i>
|
<i class="fas fa-search me-2"></i>
|
||||||
{% trans "Your application is currently under screening. We are evaluating your qualifications against the job requirements." %}
|
{% trans "Your application is currently under screening. We are evaluating your qualifications against the job requirements." %}
|
||||||
</div>
|
</div>
|
||||||
|
{% elif application.stage == 'Document Review' %}
|
||||||
|
<div class="alert alert-purple">
|
||||||
|
<i class="fas fa-file-alt me-2"></i>
|
||||||
|
{% trans "Please upload the required documents for review. Our team will evaluate your submitted materials." %}
|
||||||
|
</div>
|
||||||
{% elif application.stage == 'Exam' %}
|
{% elif application.stage == 'Exam' %}
|
||||||
<div class="alert alert-purple">
|
<div class="alert alert-purple">
|
||||||
<i class="fas fa-clipboard-check me-2"></i>
|
<i class="fas fa-clipboard-check me-2"></i>
|
||||||
|
|||||||
@ -647,30 +647,30 @@
|
|||||||
<div class="card shadow-sm mb-2 p-2">
|
<div class="card shadow-sm mb-2 p-2">
|
||||||
<h5 class="text-muted mb-3"><i class="fas fa-cog me-2"></i>{% trans "Management Actions" %}</h5>
|
<h5 class="text-muted mb-3"><i class="fas fa-cog me-2"></i>{% trans "Management Actions" %}</h5>
|
||||||
<div class="d-grid gap-2">
|
<div class="d-grid gap-2">
|
||||||
<a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-primary">
|
{% comment %} <a href="{% url 'candidate_update' candidate.slug %}" class="btn btn-outline-primary">
|
||||||
<i class="fas fa-edit"></i> {% trans "Edit Details" %}
|
<i class="fas fa-edit"></i> {% trans "Edit Details" %}
|
||||||
</a>
|
</a> {% endcomment %}
|
||||||
<a href="{% url 'candidate_delete' candidate.slug %}" class="btn btn-outline-danger" onclick="return confirm('{% trans "Are you sure you want to delete this candidate?" %}')">
|
{% comment %} <a href="{% url 'candidate_delete' candidate.slug %}" class="btn btn-outline-danger" onclick="return confirm('{% trans "Are you sure you want to delete this candidate?" %}')">
|
||||||
<i class="fas fa-trash-alt"></i> {% trans "Delete Candidate" %}
|
<i class="fas fa-trash-alt"></i> {% trans "Delete Candidate" %}
|
||||||
</a>
|
</a> {% endcomment %}
|
||||||
<a href="{% url 'candidate_list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'candidate_list' %}" class="btn btn-outline-secondary">
|
||||||
<i class="fas fa-arrow-left"></i> {% trans "Back to List" %}
|
<i class="fas fa-arrow-left"></i> {% trans "Back to List" %}
|
||||||
</a>
|
</a>
|
||||||
{% if candidate.resume %}
|
{% if candidate.resume %}
|
||||||
|
|
||||||
<a href="{{ candidate.resume.url }}" target="_blank" class="btn btn-outline-primary">
|
{% comment %} <a href="{{ candidate.resume.url }}" target="_blank" class="btn btn-outline-primary">
|
||||||
<i class="fas fa-eye me-1"></i>
|
<i class="fas fa-eye me-1"></i>
|
||||||
{% trans "View Actual Resume" %}
|
{% trans "View Actual Resume" %}
|
||||||
</a>
|
</a> {% endcomment %}
|
||||||
<a href="{{ candidate.resume.url }}" download class="btn btn-outline-primary">
|
<a href="{{ candidate.resume.url }}" download class="btn btn-outline-primary">
|
||||||
<i class="fas fa-download me-1"></i>
|
<i class="fas fa-download me-1"></i>
|
||||||
{% trans "Download Resume" %}
|
{% trans "Download Resume" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a href="{% url 'candidate_resume_template' candidate.slug %}" class="btn btn-outline-info">
|
{% comment %} <a href="{% url 'candidate_resume_template' candidate.slug %}" class="btn btn-outline-info">
|
||||||
<i class="fas fa-file-alt me-1"></i>
|
<i class="fas fa-file-alt me-1"></i>
|
||||||
{% trans "View Resume AI Overview" %}
|
{% trans "View Resume AI Overview" %}
|
||||||
</a>
|
</a> {% endcomment %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -252,7 +252,7 @@
|
|||||||
{% trans "GPA" %}
|
{% trans "GPA" %}
|
||||||
</label>
|
</label>
|
||||||
<input type="number" name="GPA" id="gpa" class="form-control form-control-sm"
|
<input type="number" name="GPA" id="gpa" class="form-control form-control-sm"
|
||||||
value="{{ gpa }}" min="0" max="4" step="1"
|
value="{{ gpa }}" min="0" max="4"
|
||||||
placeholder="e.g., 4" style="width: 120px;">
|
placeholder="e.g., 4" style="width: 120px;">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
|
|||||||
@ -69,7 +69,7 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="{{ form.first_name.id_for_label }}" class="form-label">
|
<label for="{{ form.first_name.id_for_label }}" class="form-label">
|
||||||
{% trans "First Name" %} <span class="text-danger">*</span>
|
{% trans "First Name" %} <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
@ -81,7 +81,19 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
|
<label for="{{ form.middle_name.id_for_label }}" class="form-label">
|
||||||
|
{% trans "Middle Name" %}
|
||||||
|
</label>
|
||||||
|
{{ form.middle_name }}
|
||||||
|
{% if form.middle_name.errors %}
|
||||||
|
<div class="text-danger small">
|
||||||
|
{{ form.middle_name.errors.0 }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
<label for="{{ form.last_name.id_for_label }}" class="form-label">
|
<label for="{{ form.last_name.id_for_label }}" class="form-label">
|
||||||
{% trans "Last Name" %} <span class="text-danger">*</span>
|
{% trans "Last Name" %} <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
@ -95,19 +107,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="{{ form.middle_name.id_for_label }}" class="form-label">
|
|
||||||
{% trans "Middle Name" %}
|
|
||||||
</label>
|
|
||||||
{{ form.middle_name }}
|
|
||||||
{% if form.middle_name.errors %}
|
|
||||||
<div class="text-danger small">
|
|
||||||
{{ form.middle_name.errors.0 }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
<label for="{{ form.phone.id_for_label }}" class="form-label">
|
||||||
{% trans "Phone Number" %} <span class="text-danger">*</span>
|
{% trans "Phone Number" %} <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
@ -118,21 +118,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6 mb-3">
|
|
||||||
<label for="{{ form.gpa.id_for_label }}" class="form-label">
|
|
||||||
{% trans "GPA" %} <span class="text-danger">*</span>
|
|
||||||
</label>
|
|
||||||
{{ form.gpa }}
|
|
||||||
{% if form.nationality.errors %}
|
|
||||||
<div class="text-danger small">
|
|
||||||
{{ form.gpa.errors.0 }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="{{ form.nationality.id_for_label }}" class="form-label">
|
<label for="{{ form.nationality.id_for_label }}" class="form-label">
|
||||||
{% trans "Nationality" %} <span class="text-danger">*</span>
|
{% trans "Nationality" %} <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
@ -144,7 +131,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6 mb-3">
|
<div class="col-md-4 mb-3">
|
||||||
<label for="{{ form.gender.id_for_label }}" class="form-label">
|
<label for="{{ form.gender.id_for_label }}" class="form-label">
|
||||||
{% trans "Gender" %} <span class="text-danger">*</span>
|
{% trans "Gender" %} <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
@ -215,7 +202,7 @@
|
|||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
{% trans "Already have an account?" %}
|
{% trans "Already have an account?" %}
|
||||||
<a href="{% url 'portal_login' %}" class="text-decoration-none text-kaauh-teal">
|
<a href="{% url 'account_login' %}?next={% url 'application_submit_form' job.form_template.slug %}" class="text-decoration-none text-kaauh-teal">
|
||||||
{% trans "Login here" %}
|
{% trans "Login here" %}
|
||||||
</a>
|
</a>
|
||||||
</small>
|
</small>
|
||||||
|
|||||||
@ -13,12 +13,17 @@
|
|||||||
<a href="{% url 'source_update' source.pk %}" class="btn btn-outline-secondary">
|
<a href="{% url 'source_update' source.pk %}" class="btn btn-outline-secondary">
|
||||||
<i class="fas fa-edit"></i> Edit
|
<i class="fas fa-edit"></i> Edit
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'generate_api_keys' source.pk %}" class="btn btn-warning">
|
{% comment %} <a href="{% url 'generate_api_keys' source.pk %}" class="btn btn-warning">
|
||||||
<i class="fas fa-key"></i> Generate Keys
|
<i class="fas fa-key"></i> Generate Keys
|
||||||
</a>
|
</a> {% endcomment %}
|
||||||
<button type="button"
|
<button id="toggle-source-status"
|
||||||
class="btn btn-outline-warning"
|
type="button"
|
||||||
|
class="btn btn-outline-{{ source.is_active|yesno:'warning,success' }}"
|
||||||
hx-post="{% url 'toggle_source_status' source.pk %}"
|
hx-post="{% url 'toggle_source_status' source.pk %}"
|
||||||
|
hx-target="#toggle-source-status"
|
||||||
|
hx-select="#toggle-source-status"
|
||||||
|
hx-select-oob="#source-status"
|
||||||
|
hx-swap="outerHTML"
|
||||||
hx-confirm="Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?"
|
hx-confirm="Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?"
|
||||||
title="{{ source.is_active|yesno:'Deactivate,Activate' }}">
|
title="{{ source.is_active|yesno:'Deactivate,Activate' }}">
|
||||||
<i class="fas fa-{{ source.is_active|yesno:'pause,play' }}"></i>
|
<i class="fas fa-{{ source.is_active|yesno:'pause,play' }}"></i>
|
||||||
@ -93,7 +98,7 @@
|
|||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label text-muted">Status</label>
|
<label class="form-label text-muted">Status</label>
|
||||||
<div>
|
<div id="source-status">
|
||||||
{% if source.is_active %}
|
{% if source.is_active %}
|
||||||
<span class="badge bg-success">Active</span>
|
<span class="badge bg-success">Active</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -161,7 +166,7 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h6 class="mb-0">API Credentials</h6>
|
<h6 class="mb-0">API Credentials</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div id="api-credentials" class="card-body">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label text-muted">API Key</label>
|
<label class="form-label text-muted">API Key</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
@ -190,7 +195,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-end">
|
<div class="text-end">
|
||||||
<a href="{% url 'generate_api_keys' source.pk %}" class="btn btn-warning btn-sm">
|
<a hx-post="{% url 'generate_api_keys' source.pk %}" hx-target="#api-credentials" hx-select="#api-credentials" hx-swap="outerHTML" class="btn btn-main-action btn-sm">
|
||||||
<i class="fas fa-key"></i> Generate New Keys
|
<i class="fas fa-key"></i> Generate New Keys
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -371,7 +376,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_js %}
|
{% block customJS %}
|
||||||
<script>
|
<script>
|
||||||
function toggleSecretVisibility() {
|
function toggleSecretVisibility() {
|
||||||
const secretInput = document.getElementById('api-secret');
|
const secretInput = document.getElementById('api-secret');
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static %}
|
{% load static i18n %}
|
||||||
{% load widget_tweaks %}
|
{% load widget_tweaks %}
|
||||||
|
|
||||||
{% block title %}{{ title }}{% endblock %}
|
{% block title %}{{ title }}{% endblock %}
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<label for="{{ form.source_type.id_for_label }}" class="form-label">
|
<label for="{{ form.source_type.id_for_label }}" class="form-label">
|
||||||
{{ form.source_type.label }} <span class="text-danger">*</span>
|
{{ form.source_type.label }} <span class="text-danger">*</span>
|
||||||
</label>
|
</label>
|
||||||
{{ form.source_type|add_class:"form-select" }}
|
{{ form.source_type }}
|
||||||
{% if form.source_type.errors %}
|
{% if form.source_type.errors %}
|
||||||
<div class="invalid-feedback d-block">
|
<div class="invalid-feedback d-block">
|
||||||
{% for error in form.source_type.errors %}
|
{% for error in form.source_type.errors %}
|
||||||
@ -63,7 +63,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.ip_address.id_for_label }}" class="form-label">
|
||||||
|
{{ form.ip_address.label }} <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
{{ form.ip_address|add_class:"form-control" }}
|
||||||
|
{% if form.ip_address.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{% for error in form.ip_address.errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-text">{{ form.ip_address.help_text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ form.ip_address.id_for_label }}" class="form-label">
|
||||||
|
{{ form.trusted_ips.label }} <span class="text-danger">*</span>
|
||||||
|
</label>
|
||||||
|
{{ form.trusted_ips|add_class:"form-control" }}
|
||||||
|
{% if form.trusted_ips.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{% for error in form.trusted_ips.errors %}
|
||||||
|
{{ error }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="form-text">{{ form.trusted_ips.help_text }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
<label for="{{ form.description.id_for_label }}" class="form-label">
|
<label for="{{ form.description.id_for_label }}" class="form-label">
|
||||||
{{ form.description.label }}
|
{{ form.description.label }}
|
||||||
</label>
|
</label>
|
||||||
@ -78,23 +112,6 @@
|
|||||||
<div class="form-text">{{ form.description.help_text }}</div>
|
<div class="form-text">{{ form.description.help_text }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-6">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="{{ form.ip_address.id_for_label }}" class="form-label">
|
|
||||||
{{ form.ip_address.label }}
|
|
||||||
</label>
|
|
||||||
{{ form.ip_address|add_class:"form-control" }}
|
|
||||||
{% if form.ip_address.errors %}
|
|
||||||
<div class="invalid-feedback d-block">
|
|
||||||
{% for error in form.ip_address.errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="form-text">{{ form.ip_address.help_text }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
@ -169,8 +186,8 @@
|
|||||||
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
|
<a href="{% url 'source_list' %}" class="btn btn-outline-secondary">
|
||||||
<i class="fas fa-times"></i> Cancel
|
<i class="fas fa-times"></i> Cancel
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="btn btn-primary">
|
<button type="submit" class="btn btn-main-action">
|
||||||
<i class="fas fa-save"></i> {{ button_text }}
|
<i class="fas fa-save me-1"></i> {% trans "Save" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h1 class="h3 mb-0">Sources</h1>
|
<h1 class="h3 mb-0">Sources</h1>
|
||||||
<a href="{% url 'source_create' %}" class="btn btn-primary">
|
<a href="{% url 'source_create' %}" class="btn btn-main-action">
|
||||||
<i class="fas fa-plus"></i> Create Source
|
<i class="fas fa-plus"></i> Create Source
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -71,12 +71,10 @@
|
|||||||
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none">
|
<a href="{% url 'source_detail' source.pk %}" class="text-decoration-none">
|
||||||
<strong>{{ source.name }}</strong>
|
<strong>{{ source.name }}</strong>
|
||||||
</a>
|
</a>
|
||||||
{% if source.description %}
|
|
||||||
<br><small class="text-muted">{{ source.description|truncatechars:50 }}</small>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-info">{{ source.get_source_type_display }}</span>
|
<span class="badge bg-info">{{ source.source_type }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if source.is_active %}
|
{% if source.is_active %}
|
||||||
@ -101,17 +99,17 @@
|
|||||||
class="btn btn-sm btn-outline-secondary" title="Edit">
|
class="btn btn-sm btn-outline-secondary" title="Edit">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<button type="button"
|
{% comment %} <button type="button"
|
||||||
class="btn btn-sm btn-outline-warning"
|
class="btn btn-sm btn-outline-warning"
|
||||||
hx-post="{% url 'toggle_source_status' source.pk %}"
|
hx-post="{% url 'toggle_source_status' source.pk %}"
|
||||||
hx-confirm="Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?"
|
hx-confirm="Are you sure you want to {{ source.is_active|yesno:'deactivate,activate' }} this source?"
|
||||||
title="{{ source.is_active|yesno:'Deactivate,Activate' }}">
|
title="{{ source.is_active|yesno:'Deactivate,Activate' }}">
|
||||||
<i class="fas fa-{{ source.is_active|yesno:'pause,play' }}"></i>
|
<i class="fas fa-{{ source.is_active|yesno:'pause,play' }}"></i>
|
||||||
</button>
|
</button> {% endcomment %}
|
||||||
<a href="{% url 'source_delete' source.pk %}"
|
{% comment %} <a href="{% url 'source_delete' source.pk %}"
|
||||||
class="btn btn-sm btn-outline-danger" title="Delete">
|
class="btn btn-sm btn-outline-danger" title="Delete">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</a>
|
</a> {% endcomment %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -175,7 +173,7 @@
|
|||||||
Get started by creating your first source.
|
Get started by creating your first source.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<a href="{% url 'source_create' %}" class="btn btn-primary">
|
<a href="{% url 'source_create' %}" class="btn btn-main-action">
|
||||||
<i class="fas fa-plus"></i> Create Source
|
<i class="fas fa-plus"></i> Create Source
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user