Merge branch 'main' of http://10.10.1.136:3000/marwan/kaauh_ats into frontend
This commit is contained in:
commit
5b114b630e
6
.env
6
.env
@ -1,3 +1,3 @@
|
|||||||
DB_NAME=haikal_db
|
DB_NAME=norahuniversity
|
||||||
DB_USER=faheed
|
DB_USER=norahuniversity
|
||||||
DB_PASSWORD=Faheed@215
|
DB_PASSWORD=norahuniversity
|
||||||
9882
django.po.bkp
Normal file
9882
django.po.bkp
Normal file
File diff suppressed because it is too large
Load Diff
9885
django2.po
Normal file
9885
django2.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -25,6 +25,7 @@ class ERPIntegrationService:
|
|||||||
Validate the incoming request from ERP system
|
Validate the incoming request from ERP system
|
||||||
Returns: (is_valid, error_message)
|
Returns: (is_valid, error_message)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Check if source is active
|
# Check if source is active
|
||||||
if not self.source.is_active:
|
if not self.source.is_active:
|
||||||
return False, "Source is not active"
|
return False, "Source is not active"
|
||||||
@ -70,6 +71,7 @@ class ERPIntegrationService:
|
|||||||
try:
|
try:
|
||||||
# Map ERP fields to JobPosting fields
|
# Map ERP fields to JobPosting fields
|
||||||
job_data = {
|
job_data = {
|
||||||
|
'internal_job_id': request_data.get('job_id', '').strip(),
|
||||||
'title': request_data.get('title', '').strip(),
|
'title': request_data.get('title', '').strip(),
|
||||||
'department': request_data.get('department', '').strip(),
|
'department': request_data.get('department', '').strip(),
|
||||||
'job_type': self.map_job_type(request_data.get('job_type', 'FULL_TIME')),
|
'job_type': self.map_job_type(request_data.get('job_type', 'FULL_TIME')),
|
||||||
|
|||||||
@ -269,7 +269,7 @@ class SourceAdvancedForm(forms.ModelForm):
|
|||||||
class PersonForm(forms.ModelForm):
|
class PersonForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Person
|
model = Person
|
||||||
fields = ["first_name","middle_name", "last_name", "email", "phone","date_of_birth","nationality","address","gender"]
|
fields = ["first_name","middle_name", "last_name", "email", "phone","date_of_birth","nationality","gender","address"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"first_name": forms.TextInput(attrs={'class': 'form-control'}),
|
"first_name": forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
"middle_name": forms.TextInput(attrs={'class': 'form-control'}),
|
"middle_name": forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
@ -591,7 +591,6 @@ class JobPostingForm(forms.ModelForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"class": "form-control",
|
"class": "form-control",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"placeholder": "Maximum number of applicants",
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
@ -834,9 +833,9 @@ class ProfileImageUploadForm(forms.ModelForm):
|
|||||||
|
|
||||||
|
|
||||||
class StaffUserCreationForm(UserCreationForm):
|
class StaffUserCreationForm(UserCreationForm):
|
||||||
email = forms.EmailField(required=True)
|
email = forms.EmailField(label=_("Email"), required=True)
|
||||||
first_name = forms.CharField(max_length=30, required=True)
|
first_name = forms.CharField(label=_("First Name"),max_length=30, required=True)
|
||||||
last_name = forms.CharField(max_length=150, required=True)
|
last_name = forms.CharField(label=_("Last Name"),max_length=150, required=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
@ -1078,7 +1077,6 @@ class AgencyJobAssignmentForm(forms.ModelForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"class": "form-control",
|
"class": "form-control",
|
||||||
"min": 1,
|
"min": 1,
|
||||||
"placeholder": "Maximum number of candidates",
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
"deadline_date": forms.DateTimeInput(
|
"deadline_date": forms.DateTimeInput(
|
||||||
@ -1090,7 +1088,6 @@ class AgencyJobAssignmentForm(forms.ModelForm):
|
|||||||
attrs={
|
attrs={
|
||||||
"class": "form-control",
|
"class": "form-control",
|
||||||
"rows": 3,
|
"rows": 3,
|
||||||
"placeholder": "Internal notes about this assignment",
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
18
recruitment/migrations/0006_alter_customuser_email.py
Normal file
18
recruitment/migrations/0006_alter_customuser_email.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2.6 on 2025-11-23 12:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('recruitment', '0005_alter_interviewschedule_template_location'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='customuser',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(error_messages={'unique': 'A user with this email already exists.'}, max_length=254, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -47,6 +47,12 @@ class CustomUser(AbstractUser):
|
|||||||
designation = models.CharField(
|
designation = models.CharField(
|
||||||
max_length=100, blank=True, null=True, verbose_name=_("Designation")
|
max_length=100, blank=True, null=True, verbose_name=_("Designation")
|
||||||
)
|
)
|
||||||
|
email = models.EmailField(
|
||||||
|
unique=True,
|
||||||
|
error_messages={
|
||||||
|
"unique": _("A user with this email already exists."),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("User")
|
verbose_name = _("User")
|
||||||
@ -487,7 +493,6 @@ class Person(Base):
|
|||||||
unique=True,
|
unique=True,
|
||||||
db_index=True,
|
db_index=True,
|
||||||
verbose_name=_("Email"),
|
verbose_name=_("Email"),
|
||||||
help_text=_("Unique email address for the person"),
|
|
||||||
)
|
)
|
||||||
phone = models.CharField(
|
phone = models.CharField(
|
||||||
max_length=20, blank=True, null=True, verbose_name=_("Phone")
|
max_length=20, blank=True, null=True, verbose_name=_("Phone")
|
||||||
|
|||||||
@ -187,7 +187,6 @@ class PersonCreateView(CreateView):
|
|||||||
template_name = "people/create_person.html"
|
template_name = "people/create_person.html"
|
||||||
form_class = PersonForm
|
form_class = PersonForm
|
||||||
success_url = reverse_lazy("person_list")
|
success_url = reverse_lazy("person_list")
|
||||||
print("from agency")
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
if "HX-Request" in self.request.headers:
|
if "HX-Request" in self.request.headers:
|
||||||
instance = form.save()
|
instance = form.save()
|
||||||
@ -196,7 +195,6 @@ class PersonCreateView(CreateView):
|
|||||||
slug = self.request.POST.get("agency")
|
slug = self.request.POST.get("agency")
|
||||||
if slug:
|
if slug:
|
||||||
agency = HiringAgency.objects.get(slug=slug)
|
agency = HiringAgency.objects.get(slug=slug)
|
||||||
print(agency)
|
|
||||||
instance.agency = agency
|
instance.agency = agency
|
||||||
instance.save()
|
instance.save()
|
||||||
return redirect("agency_portal_persons_list")
|
return redirect("agency_portal_persons_list")
|
||||||
@ -867,7 +865,10 @@ def kaauh_career(request):
|
|||||||
# job detail facing the candidate:
|
# job detail facing the candidate:
|
||||||
def application_detail(request, slug):
|
def application_detail(request, slug):
|
||||||
job = get_object_or_404(JobPosting, slug=slug)
|
job = get_object_or_404(JobPosting, slug=slug)
|
||||||
return render(request, "applicant/application_detail.html", {"job": job})
|
already_applied = False
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
already_applied = Application.objects.filter(job=job,person=request.user.person_profile).exists()
|
||||||
|
return render(request, "applicant/application_detail.html", {"job": job,"already_applied":already_applied})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -1202,7 +1203,13 @@ def application_submit_form(request, template_slug):
|
|||||||
"""Display the form as a step-by-step wizard"""
|
"""Display the form as a step-by-step wizard"""
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return redirect("candidate_signup",slug=template_slug)
|
return redirect("candidate_signup",slug=template_slug)
|
||||||
|
|
||||||
template = get_object_or_404(FormTemplate, slug=template_slug, is_active=True)
|
template = get_object_or_404(FormTemplate, slug=template_slug, is_active=True)
|
||||||
|
|
||||||
|
if Application.objects.filter(job=template.job,person=request.user.person_profile).exists():
|
||||||
|
messages.error(request, _("You have already submitted an application for this job."))
|
||||||
|
return redirect("application_detail",slug=template.job.slug)
|
||||||
|
|
||||||
stage = template.stages.filter(name="Contact Information")
|
stage = template.stages.filter(name="Contact Information")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -202,11 +202,14 @@ class ApplicationCreateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessa
|
|||||||
job = get_object_or_404(models.JobPosting, slug=self.kwargs['slug'])
|
job = get_object_or_404(models.JobPosting, slug=self.kwargs['slug'])
|
||||||
form.instance.job = job
|
form.instance.job = job
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
def form_invalid(self, form):
|
||||||
|
messages.error(self.request, f"{form.errors.as_text()}")
|
||||||
|
return super().form_invalid(form)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
if self.request.method == 'GET':
|
# if self.request.method == 'GET':
|
||||||
context['person_form'] = forms.PersonForm()
|
context['person_form'] = forms.PersonForm()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
class ApplicationUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView):
|
class ApplicationUpdateView(LoginRequiredMixin, StaffRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||||
|
|||||||
@ -81,6 +81,17 @@ class ERPIntegrationView(View):
|
|||||||
'message': 'Source not found'
|
'message': 'Source not found'
|
||||||
}, status=404)
|
}, status=404)
|
||||||
|
|
||||||
|
job_id = data.get('job_id')
|
||||||
|
if not job_id:
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Job ID is required and must be unique'
|
||||||
|
})
|
||||||
|
if JobPosting.objects.filter(internal_job_id=job_id).exists():
|
||||||
|
return JsonResponse({
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'Job with this ID already exists'
|
||||||
|
}, status=400)
|
||||||
# Create integration service
|
# Create integration service
|
||||||
service = ERPIntegrationService(source)
|
service = ERPIntegrationService(source)
|
||||||
|
|
||||||
@ -144,6 +155,7 @@ class ERPIntegrationView(View):
|
|||||||
def _create_job(self, service: ERPIntegrationService, data: Dict[str, Any]) -> tuple[Dict[str, Any], str]:
|
def _create_job(self, service: ERPIntegrationService, data: Dict[str, Any]) -> tuple[Dict[str, Any], str]:
|
||||||
"""Create a new job from ERP data"""
|
"""Create a new job from ERP data"""
|
||||||
# Validate ERP data
|
# Validate ERP data
|
||||||
|
# print(data)
|
||||||
is_valid, error_msg = service.validate_erp_data(data)
|
is_valid, error_msg = service.validate_erp_data(data)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
return None, error_msg
|
return None, error_msg
|
||||||
@ -152,7 +164,6 @@ class ERPIntegrationView(View):
|
|||||||
job, error_msg = service.create_job_from_erp(data)
|
job, error_msg = service.create_job_from_erp(data)
|
||||||
if error_msg:
|
if error_msg:
|
||||||
return None, error_msg
|
return None, error_msg
|
||||||
|
|
||||||
# Prepare response data
|
# Prepare response data
|
||||||
response_data = {
|
response_data = {
|
||||||
'job_id': job.internal_job_id,
|
'job_id': job.internal_job_id,
|
||||||
|
|||||||
@ -36,12 +36,22 @@
|
|||||||
<p class="text-muted small mb-3">{% trans "Review the full job details below before submitting your application." %}</p>
|
<p class="text-muted small mb-3">{% trans "Review the full job details below before submitting your application." %}</p>
|
||||||
|
|
||||||
{% if job.form_template %}
|
{% if job.form_template %}
|
||||||
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100 shadow-sm">
|
{% if user.is_authenticated and already_applied %}
|
||||||
|
<button class="btn btn-main-action btn-lg w-100" disabled>
|
||||||
|
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% comment %} <a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100 shadow-sm">
|
||||||
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
||||||
</a>
|
</a>
|
||||||
{% elif not job.is_expired %}
|
{% elif not job.is_expired %}
|
||||||
<p class="text-danger fw-bold">{% trans "Application form is unavailable." %}</p>
|
<p class="text-danger fw-bold">{% trans "Application form is unavailable." %}</p>
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -196,7 +206,6 @@
|
|||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
@ -205,11 +214,17 @@
|
|||||||
|
|
||||||
{# 📱 MOBILE FIXED APPLY BAR (Replaced inline style with utility classes) #}
|
{# 📱 MOBILE FIXED APPLY BAR (Replaced inline style with utility classes) #}
|
||||||
{% if job.form_template %}
|
{% if job.form_template %}
|
||||||
<footer class="fixed-bottom d-lg-none bg-white border-top shadow-lg p-3">
|
<footer class="fixed-bottom d-lg-none bg-white border-top shadow-lg p-3">
|
||||||
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100">
|
{% if user.is_authenticated and already_applied %}
|
||||||
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
<button class="btn btn-main-action btn-lg w-100" disabled>
|
||||||
</a>
|
<i class="fas fa-paper-plane me-2"></i> {% trans "You already applied for this position" %}
|
||||||
</footer>
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<a href="{% url 'application_submit_form' job.form_template.slug %}" class="btn btn-main-action btn-lg w-100">
|
||||||
|
<i class="fas fa-paper-plane me-2"></i> {% trans "Apply for this Position" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</footer>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -123,14 +123,16 @@
|
|||||||
</li> {% endcomment %}
|
</li> {% endcomment %}
|
||||||
<li class="nav-item me-2">
|
<li class="nav-item me-2">
|
||||||
{% if LANGUAGE_CODE == 'en' %}
|
{% if LANGUAGE_CODE == 'en' %}
|
||||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
<form action="{% url 'set_language' %}" method="post" class="d-inline">
|
||||||
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||||
<button name="language" value="ar" class="btn bg-primary-theme text-white" type="submit">
|
<button name="language" value="ar" class="btn bg-primary-theme text-white" type="submit">
|
||||||
<span class="me-2">🇸🇦</span> العربية
|
<span class="me-2">🇸🇦</span> العربية
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{% elif LANGUAGE_CODE == 'ar' %}
|
{% elif LANGUAGE_CODE == 'ar' %}
|
||||||
<form action="{% url 'set_language' %}" method="post" class="d-inline">{% csrf_token %}
|
<form action="{% url 'set_language' %}" method="post" class="d-inline">
|
||||||
|
{% csrf_token %}
|
||||||
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
<input name="next" type="hidden" value="{{ request.get_full_path }}">
|
||||||
<button name="language" value="en" class="btn bg-primary-theme text-white" type="submit">
|
<button name="language" value="en" class="btn bg-primary-theme text-white" type="submit">
|
||||||
<span class="me-2">🇺🇸</span> English
|
<span class="me-2">🇺🇸</span> English
|
||||||
|
|||||||
@ -1249,7 +1249,7 @@ const elements = {
|
|||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
state.templateId = result.template_slug;
|
state.templateId = result.template_slug;
|
||||||
window.location.href = "{% url 'form_templates_list' %}";
|
window.location.href = "{% url 'job_detail' template.job.slug %}";
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
alert('Error saving form template: ' + result.error);
|
alert('Error saving form template: ' + result.error);
|
||||||
|
|||||||
@ -361,7 +361,10 @@
|
|||||||
{% trans "Manage the custom application forms associated with this job posting." %}
|
{% trans "Manage the custom application forms associated with this job posting." %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if not job.form_template %}
|
<a href="{% url 'form_builder' job.form_template.slug %}" class="btn btn-outline-secondary w-100">
|
||||||
|
<i class="fas fa-list-alt me-1"></i> {% trans "Manage Job Form" %}
|
||||||
|
</a>
|
||||||
|
{% comment %} {% if not job.form_template %}
|
||||||
<a href="{% url 'create_form_template' %}" class="btn btn-main-action">
|
<a href="{% url 'create_form_template' %}" class="btn btn-main-action">
|
||||||
<i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %}
|
<i class="fas fa-plus-circle me-1"></i> {% trans "Create New Form Template" %}
|
||||||
</a>
|
</a>
|
||||||
@ -377,7 +380,7 @@
|
|||||||
<p>{% trans "This job status is not active, the form will appear once the job is made active"%}</p>
|
<p>{% trans "This job status is not active, the form will appear once the job is made active"%}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% endif %}
|
{% endif %} {% endcomment %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -276,7 +276,8 @@
|
|||||||
<th scope="col" rowspan="2">{% trans "Source" %}</th>
|
<th scope="col" rowspan="2">{% trans "Source" %}</th>
|
||||||
<th scope="col" rowspan="2">{% trans "Max Apps" %}</th>
|
<th scope="col" rowspan="2">{% trans "Max Apps" %}</th>
|
||||||
<th scope="col" rowspan="2">{% trans "Deadline" %}</th>
|
<th scope="col" rowspan="2">{% trans "Deadline" %}</th>
|
||||||
<th scope="col" rowspan="2">{% trans "Submission" %}</th>
|
<th scope="col" rowspan="2">{% trans "Assigned To" %}</th>
|
||||||
|
<th scope="col" rowspan="2"></th>
|
||||||
|
|
||||||
|
|
||||||
<th scope="col" colspan="6" class="candidate-management-header-title">
|
<th scope="col" colspan="6" class="candidate-management-header-title">
|
||||||
@ -306,11 +307,20 @@
|
|||||||
<td>{{ job.get_source }}</td>
|
<td>{{ job.get_source }}</td>
|
||||||
<td>{{ job.max_applications }}</td>
|
<td>{{ job.max_applications }}</td>
|
||||||
<td>{{ job.application_deadline|date:"d-m-Y" }}</td>
|
<td>{{ job.application_deadline|date:"d-m-Y" }}</td>
|
||||||
|
{% if job.assigned_to %}
|
||||||
|
<td>
|
||||||
|
<span class="badge bg-primary-theme">
|
||||||
|
{{ job.assigned_to.first_name|capfirst }} {{ job.assigned_to.last_name|capfirst }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{% trans "Unassigned" %}</td>
|
||||||
|
{% endif %}
|
||||||
<td>
|
<td>
|
||||||
{% if job.form_template %}
|
{% if job.form_template %}
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<a href="{% url 'form_template_submissions_list' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'All Application Submissions' %}">
|
<a href="{% url 'form_template_submissions_list' job.form_template.slug %}" class="btn btn-outline-secondary" title="{% trans 'All Application Submissions' %}">
|
||||||
<i class="fas fa-file-alt text-primary-theme"></i>
|
<i class="fas fa-file-alt text-primary-theme"></i>{% trans "Submissions" %}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load static i18n crispy_forms_tags %}
|
{% load static i18n crispy_forms_tags %}
|
||||||
|
|
||||||
{% block title %}Create Person - {{ block.super }}{% endblock %}
|
{% block title %}Create Applicant - {{ block.super }}{% endblock %}
|
||||||
|
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
<style>
|
<style>
|
||||||
@ -184,9 +184,9 @@
|
|||||||
|
|
||||||
<form method="post" enctype="multipart/form-data" id="person-form">
|
<form method="post" enctype="multipart/form-data" id="person-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
{{form|crispy}}
|
||||||
<!-- Profile Image Section -->
|
<!-- Profile Image Section -->
|
||||||
<div class="row mb-4">
|
{% comment %} <div class="row mb-4">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div class="profile-image-upload" onclick="document.getElementById('id_profile_image').click()">
|
<div class="profile-image-upload" onclick="document.getElementById('id_profile_image').click()">
|
||||||
<div id="image-preview-container">
|
<div id="image-preview-container">
|
||||||
@ -261,7 +261,7 @@
|
|||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
{{ form.address }}
|
{{ form.address }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> {% endcomment %}
|
||||||
|
|
||||||
<!-- LinkedIn Profile Section -->
|
<!-- LinkedIn Profile Section -->
|
||||||
{% comment %} <div class="row mb-4">
|
{% comment %} <div class="row mb-4">
|
||||||
@ -292,11 +292,8 @@
|
|||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
<button type="reset" class="btn btn-outline-secondary">
|
|
||||||
<i class="fas fa-undo me-1"></i> {% trans "Reset" %}
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-main-action">
|
<button type="submit" class="btn btn-main-action">
|
||||||
<i class="fas fa-save me-1"></i> {% trans "Create Person" %}
|
<i class="fas fa-save me-1"></i> {% trans "Create Applicant" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -308,141 +305,3 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block customJS %}
|
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Profile Image Preview
|
|
||||||
const profileImageInput = document.getElementById('id_profile_image');
|
|
||||||
const imagePreviewContainer = document.getElementById('image-preview-container');
|
|
||||||
|
|
||||||
profileImageInput.addEventListener('change', function(e) {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file && file.type.startsWith('image/')) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function(e) {
|
|
||||||
imagePreviewContainer.innerHTML = `
|
|
||||||
<img src="${e.target.result}" alt="Profile Preview" class="profile-image-preview">
|
|
||||||
<h5 class="text-muted mt-3">${file.name}</h5>
|
|
||||||
<p class="text-muted small">{% trans "Click to change photo" %}</p>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Form Validation
|
|
||||||
const form = document.getElementById('person-form');
|
|
||||||
form.addEventListener('submit', function(e) {
|
|
||||||
const submitBtn = form.querySelector('button[type="submit"]');
|
|
||||||
submitBtn.classList.add('loading');
|
|
||||||
submitBtn.disabled = true;
|
|
||||||
|
|
||||||
// Basic validation
|
|
||||||
const firstName = document.getElementById('id_first_name').value.trim();
|
|
||||||
const lastName = document.getElementById('id_last_name').value.trim();
|
|
||||||
const email = document.getElementById('id_email').value.trim();
|
|
||||||
|
|
||||||
if (!firstName || !lastName) {
|
|
||||||
e.preventDefault();
|
|
||||||
submitBtn.classList.remove('loading');
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
alert('{% trans "First name and last name are required." %}');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (email && !isValidEmail(email)) {
|
|
||||||
e.preventDefault();
|
|
||||||
submitBtn.classList.remove('loading');
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
alert('{% trans "Please enter a valid email address." %}');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Email validation helper
|
|
||||||
function isValidEmail(email) {
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
return emailRegex.test(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
// LinkedIn URL validation
|
|
||||||
const linkedinInput = document.getElementById('id_linkedin_profile');
|
|
||||||
linkedinInput.addEventListener('blur', function() {
|
|
||||||
const value = this.value.trim();
|
|
||||||
if (value && !isValidLinkedInURL(value)) {
|
|
||||||
this.classList.add('is-invalid');
|
|
||||||
if (!this.nextElementSibling || !this.nextElementSibling.classList.contains('invalid-feedback')) {
|
|
||||||
const feedback = document.createElement('div');
|
|
||||||
feedback.className = 'invalid-feedback';
|
|
||||||
feedback.textContent = '{% trans "Please enter a valid LinkedIn URL" %}';
|
|
||||||
this.parentNode.appendChild(feedback);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.classList.remove('is-invalid');
|
|
||||||
const feedback = this.parentNode.querySelector('.invalid-feedback');
|
|
||||||
if (feedback) feedback.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function isValidLinkedInURL(url) {
|
|
||||||
const linkedinRegex = /^https?:\/\/(www\.)?linkedin\.com\/.+/i;
|
|
||||||
return linkedinRegex.test(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drag and Drop functionality
|
|
||||||
const uploadArea = document.querySelector('.profile-image-upload');
|
|
||||||
|
|
||||||
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
|
||||||
uploadArea.addEventListener(eventName, preventDefaults, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
function preventDefaults(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
['dragenter', 'dragover'].forEach(eventName => {
|
|
||||||
uploadArea.addEventListener(eventName, highlight, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
['dragleave', 'drop'].forEach(eventName => {
|
|
||||||
uploadArea.addEventListener(eventName, unhighlight, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
function highlight(e) {
|
|
||||||
uploadArea.style.borderColor = 'var(--kaauh-teal)';
|
|
||||||
uploadArea.style.backgroundColor = 'var(--kaauh-gray-light)';
|
|
||||||
}
|
|
||||||
|
|
||||||
function unhighlight(e) {
|
|
||||||
uploadArea.style.borderColor = 'var(--kaauh-border)';
|
|
||||||
uploadArea.style.backgroundColor = 'transparent';
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadArea.addEventListener('drop', handleDrop, false);
|
|
||||||
|
|
||||||
function handleDrop(e) {
|
|
||||||
const dt = e.dataTransfer;
|
|
||||||
const files = dt.files;
|
|
||||||
|
|
||||||
if (files.length > 0) {
|
|
||||||
profileImageInput.files = files;
|
|
||||||
const event = new Event('change', { bubbles: true });
|
|
||||||
profileImageInput.dispatchEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" />
|
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
|
||||||
<script>
|
|
||||||
$(document).ready(function() {
|
|
||||||
$('.select2').select2();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@ -121,7 +121,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% comment %} <li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown"
|
<a class="nav-link dropdown-toggle text-white" href="#" role="button" data-bs-toggle="dropdown"
|
||||||
data-bs-offset="0, 8" aria-expanded="false" aria-label="{% trans 'Toggle language menu' %}">
|
data-bs-offset="0, 8" aria-expanded="false" aria-label="{% trans 'Toggle language menu' %}">
|
||||||
<i class="fas fa-globe me-1"></i>
|
<i class="fas fa-globe me-1"></i>
|
||||||
|
|||||||
@ -104,7 +104,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
<h1 class="h3 mb-1" style="color: var(--kaauh-teal-dark); font-weight: 700;">
|
||||||
<i class="fas fa-tasks me-2"></i>
|
<i class="fas fa-tasks me-2"></i>
|
||||||
{{ title }}
|
{% trans "Create New Assignment" %}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-muted mb-0">
|
<p class="text-muted mb-0">
|
||||||
{% trans "Assign a job to an external hiring agency" %}
|
{% trans "Assign a job to an external hiring agency" %}
|
||||||
@ -213,7 +213,7 @@
|
|||||||
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
<i class="fas fa-times me-1"></i> {% trans "Cancel" %}
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="btn btn-main-action">
|
<button type="submit" class="btn btn-main-action">
|
||||||
<i class="fas fa-save me-1"></i> {{ button_text }}
|
<i class="fas fa-save me-1"></i> {% trans "Create Assignment" %}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -279,7 +279,7 @@
|
|||||||
<th scope="col" >{% trans "Major" %}</th>
|
<th scope="col" >{% trans "Major" %}</th>
|
||||||
<th scope="col" >{% trans "Stage" %}</th>
|
<th scope="col" >{% trans "Stage" %}</th>
|
||||||
<th scope="col">{% trans "Hiring Source" %}</th>
|
<th scope="col">{% trans "Hiring Source" %}</th>
|
||||||
<th scope="col" >{% trans "created At" %}</th>
|
<th scope="col" >{% trans "Created At" %}</th>
|
||||||
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
<th scope="col" class="text-end">{% trans "Actions" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
<div class="form-card">
|
<div class="form-card">
|
||||||
|
|
||||||
<h2 id="form-title" class="h3 fw-bold mb-4 text-center">
|
<h2 id="form-title" class="h3 fw-bold mb-4 text-center">
|
||||||
<i class="fas fa-user-plus me-2 text-accent"></i>{% trans "Create Staff User" %}
|
<i class="fas fa-user-plus me-2 text-accent"></i>{% trans "Create User" %}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
@ -66,7 +66,7 @@
|
|||||||
--bs-btn-active-border-color: #004d55;
|
--bs-btn-active-border-color: #004d55;
|
||||||
--bs-btn-focus-shadow-rgb: 40, 167, 69;
|
--bs-btn-focus-shadow-rgb: 40, 167, 69;
|
||||||
--bs-btn-color: #ffffff;">
|
--bs-btn-color: #ffffff;">
|
||||||
<i class="fas fa-save me-2"></i>{% trans "Create Staff User" %}
|
<i class="fas fa-save me-2"></i>{% trans "Create User" %}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user