few issue resolved and some ui incosistency resolved

This commit is contained in:
Faheed 2025-12-11 20:16:05 +03:00
parent 22978a4af5
commit 5db2e09ac8
15 changed files with 77 additions and 519 deletions

6
.env
View File

@ -1,3 +1,3 @@
DB_NAME=norahuniversity
DB_USER=norahuniversity
DB_PASSWORD=norahuniversity
DB_NAME=haikal_db
DB_USER=faheed
DB_PASSWORD=Faheed@215

View File

@ -318,14 +318,7 @@ class PersonForm(forms.ModelForm):
pass
return email.strip()
def clean_gpa(self):
gpa=self.cleaned_data.get('gpa')
pass
class ApplicationForm(forms.ModelForm):
@ -786,6 +779,8 @@ class StaffUserCreationForm(UserCreationForm):
user.first_name = self.cleaned_data["first_name"]
user.last_name = self.cleaned_data["last_name"]
user.username = self.generate_username(user.email)
user.password1=self.cleaned_data["password1"]
user.password2=self.cleaned_data["password2"]
user.is_staff = True
if commit:
user.save()

View File

@ -1,4 +1,4 @@
# Generated by Django 5.2.7 on 2025-12-11 11:55
# Generated by Django 5.2.7 on 2025-12-11 14:18
import django.contrib.auth.models
import django.contrib.auth.validators
@ -97,6 +97,8 @@ class Migration(migrations.Migration):
('start_time', models.DateTimeField(db_index=True, verbose_name='Start Time')),
('duration', models.PositiveIntegerField(verbose_name='Duration (minutes)')),
('status', models.CharField(choices=[('waiting', 'Waiting'), ('started', 'Started'), ('ended', 'Ended'), ('cancelled', 'Cancelled')], db_index=True, default='waiting', max_length=20)),
('cancelled_at', models.DateTimeField(blank=True, null=True, verbose_name='Cancelled At')),
('cancelled_reason', models.TextField(blank=True, null=True, verbose_name='Cancellation Reason')),
('meeting_id', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='External Meeting ID')),
('password', models.CharField(blank=True, max_length=20, null=True)),
('zoom_gateway_response', models.JSONField(blank=True, null=True)),
@ -189,7 +191,7 @@ class Migration(migrations.Migration):
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('first_name', secured_fields.fields.EncryptedCharField(blank=True, max_length=150, verbose_name='first name')),
('user_type', models.CharField(choices=[('staff', 'Staff'), ('agency', 'Agency'), ('candidate', 'Candidate')], default='staff', max_length=20, verbose_name='User Type')),
('user_type', models.CharField(choices=[('staff', 'Staff'), ('agency', 'Agency'), ('candidate', 'Candidate')], db_index=True, default='staff', max_length=20, verbose_name='User Type')),
('phone', secured_fields.fields.EncryptedCharField(blank=True, null=True, verbose_name='Phone')),
('profile_image', models.ImageField(blank=True, null=True, upload_to='profile_pic/', validators=[recruitment.validators.validate_image_size], verbose_name='Profile Image')),
('designation', models.CharField(blank=True, max_length=100, null=True, verbose_name='Designation')),
@ -586,7 +588,7 @@ class Migration(migrations.Migration):
('phone', secured_fields.fields.EncryptedCharField(blank=True, null=True, verbose_name='Phone')),
('date_of_birth', models.DateField(blank=True, null=True, verbose_name='Date of Birth')),
('gender', models.CharField(blank=True, choices=[('M', 'Male'), ('F', 'Female')], max_length=1, null=True, verbose_name='Gender')),
('gpa', models.DecimalField(decimal_places=2, help_text='GPA must be between 0 and 4.', max_digits=3, verbose_name='GPA')),
('gpa', models.DecimalField(decimal_places=2, help_text='GPA must be between 0 and 4.', max_digits=3, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(4)], verbose_name='GPA')),
('national_id', secured_fields.fields.EncryptedCharField(help_text='Enter the national id or iqama number')),
('nationality', django_countries.fields.CountryField(blank=True, max_length=2, null=True, verbose_name='Nationality')),
('address', models.TextField(blank=True, null=True, verbose_name='Address')),

View File

@ -1,199 +0,0 @@
# Generated by Django 6.0 on 2025-12-10 21:04
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='formfield',
options={'ordering': ['order']},
),
migrations.AlterModelOptions(
name='formstage',
options={'ordering': ['order']},
),
migrations.AlterModelOptions(
name='formtemplate',
options={'ordering': ['-created_at']},
),
migrations.RemoveIndex(
model_name='formtemplate',
name='recruitment_created_c21775_idx',
),
migrations.RemoveIndex(
model_name='formtemplate',
name='recruitment_is_acti_ae5efb_idx',
),
migrations.RenameField(
model_name='formfield',
old_name='required_message',
new_name='error_message',
),
migrations.AlterUniqueTogether(
name='formfield',
unique_together={('stage', 'order')},
),
migrations.AddField(
model_name='formfield',
name='help_text',
field=models.CharField(blank=True, max_length=200),
),
migrations.AddField(
model_name='formfield',
name='max_date',
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name='formfield',
name='min_date',
field=models.DateField(blank=True, null=True),
),
migrations.AlterField(
model_name='formfield',
name='field_type',
field=models.CharField(choices=[('text', 'Text Input'), ('email', 'Email'), ('phone', 'Phone'), ('textarea', 'Text Area'), ('file', 'File Upload'), ('date', 'Date Picker'), ('select', 'Dropdown'), ('radio', 'Radio Buttons'), ('checkbox', 'Checkboxes'), ('number', 'Number')], max_length=20),
),
migrations.AlterField(
model_name='formfield',
name='file_types',
field=models.CharField(blank=True, default='.pdf,.doc,.docx,.jpg,.jpeg,.png', max_length=200),
),
migrations.AlterField(
model_name='formfield',
name='is_predefined',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='formfield',
name='label',
field=models.CharField(max_length=200),
),
migrations.AlterField(
model_name='formfield',
name='max_file_size',
field=models.PositiveIntegerField(default=5, help_text='Maximum file size in MB'),
),
migrations.AlterField(
model_name='formfield',
name='max_files',
field=models.PositiveIntegerField(default=1),
),
migrations.AlterField(
model_name='formfield',
name='max_length',
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='formfield',
name='min_length',
field=models.PositiveIntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='formfield',
name='multiple_files',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='formfield',
name='options',
field=models.JSONField(blank=True, default=list),
),
migrations.AlterField(
model_name='formfield',
name='order',
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name='formfield',
name='placeholder',
field=models.CharField(blank=True, max_length=200),
),
migrations.AlterField(
model_name='formfield',
name='required',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='formfield',
name='validation_pattern',
field=models.CharField(blank=True, choices=[('', 'None'), ('email', 'Email'), ('phone', 'Phone'), ('url', 'URL'), ('number', 'Number'), ('alpha', 'Letters Only'), ('alphanum', 'Letters & Numbers'), ('custom', 'Custom Pattern')], max_length=50),
),
migrations.AlterField(
model_name='formstage',
name='is_predefined',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='formstage',
name='name',
field=models.CharField(max_length=200),
),
migrations.AlterField(
model_name='formstage',
name='order',
field=models.PositiveIntegerField(default=0),
),
migrations.AlterField(
model_name='formtemplate',
name='created_at',
field=models.DateTimeField(auto_now_add=True),
),
migrations.AlterField(
model_name='formtemplate',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='formtemplate',
name='description',
field=models.TextField(blank=True),
),
migrations.AlterField(
model_name='formtemplate',
name='is_active',
field=models.BooleanField(default=True),
),
migrations.AlterField(
model_name='formtemplate',
name='name',
field=models.CharField(max_length=200),
),
migrations.AlterField(
model_name='formtemplate',
name='slug',
field=models.SlugField(blank=True, unique=True),
),
migrations.AlterField(
model_name='formtemplate',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
migrations.AlterUniqueTogether(
name='formstage',
unique_together={('template', 'order')},
),
migrations.RemoveField(
model_name='formfield',
name='is_required',
),
migrations.RemoveField(
model_name='formfield',
name='min_file_size',
),
migrations.RemoveField(
model_name='formfield',
name='min_image_height',
),
migrations.RemoveField(
model_name='formfield',
name='min_image_width',
),
]

View File

@ -1,201 +0,0 @@
# Generated by Django 6.0 on 2025-12-10 21:21
import django.db.models.deletion
import django_extensions.db.fields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0002_alter_formfield_options_alter_formstage_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='formfield',
options={'ordering': ['order'], 'verbose_name': 'Form Field', 'verbose_name_plural': 'Form Fields'},
),
migrations.AlterModelOptions(
name='formstage',
options={'ordering': ['order'], 'verbose_name': 'Form Stage', 'verbose_name_plural': 'Form Stages'},
),
migrations.AlterModelOptions(
name='formtemplate',
options={'ordering': ['-created_at'], 'verbose_name': 'Form Template', 'verbose_name_plural': 'Form Templates'},
),
migrations.RenameField(
model_name='formfield',
old_name='error_message',
new_name='required_message',
),
migrations.AlterUniqueTogether(
name='formfield',
unique_together=set(),
),
migrations.AlterUniqueTogether(
name='formstage',
unique_together=set(),
),
migrations.AddField(
model_name='formfield',
name='is_required',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='formfield',
name='min_file_size',
field=models.FloatField(blank=True, null=True),
),
migrations.AddField(
model_name='formfield',
name='min_image_height',
field=models.IntegerField(blank=True, null=True),
),
migrations.AddField(
model_name='formfield',
name='min_image_width',
field=models.IntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='formfield',
name='field_type',
field=models.CharField(choices=[('text', 'Text Input'), ('email', 'Email'), ('phone', 'Phone'), ('textarea', 'Text Area'), ('file', 'File Upload'), ('date', 'Date Picker'), ('select', 'Dropdown'), ('radio', 'Radio Buttons'), ('checkbox', 'Checkboxes')], help_text='Type of the field', max_length=20),
),
migrations.AlterField(
model_name='formfield',
name='file_types',
field=models.CharField(blank=True, help_text="Allowed file types (comma-separated, e.g., '.pdf,.doc,.docx')", max_length=200),
),
migrations.AlterField(
model_name='formfield',
name='is_predefined',
field=models.BooleanField(default=False, help_text='Whether this is a default field'),
),
migrations.AlterField(
model_name='formfield',
name='label',
field=models.CharField(help_text='Label for the field', max_length=200),
),
migrations.AlterField(
model_name='formfield',
name='max_file_size',
field=models.PositiveIntegerField(default=5, help_text='Maximum file size in MB (default: 5MB)'),
),
migrations.AlterField(
model_name='formfield',
name='max_files',
field=models.PositiveIntegerField(default=1, help_text='Maximum number of files allowed (when multiple_files is True)'),
),
migrations.AlterField(
model_name='formfield',
name='max_length',
field=models.IntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='formfield',
name='min_length',
field=models.IntegerField(blank=True, null=True),
),
migrations.AlterField(
model_name='formfield',
name='multiple_files',
field=models.BooleanField(default=False, help_text='Allow multiple files to be uploaded'),
),
migrations.AlterField(
model_name='formfield',
name='options',
field=models.JSONField(blank=True, default=list, help_text='Options for selection fields (stored as JSON array)'),
),
migrations.AlterField(
model_name='formfield',
name='order',
field=models.PositiveIntegerField(default=0, help_text='Order of the field in the stage'),
),
migrations.AlterField(
model_name='formfield',
name='placeholder',
field=models.CharField(blank=True, help_text='Placeholder text', max_length=200),
),
migrations.AlterField(
model_name='formfield',
name='required',
field=models.BooleanField(default=False, help_text='Whether the field is required'),
),
migrations.AlterField(
model_name='formfield',
name='validation_pattern',
field=models.CharField(blank=True, choices=[('', 'None'), ('email', 'Email'), ('phone', 'Phone'), ('url', 'URL'), ('number', 'Number'), ('alpha', 'Letters Only'), ('alphanum', 'Letters & Numbers'), ('custom', 'Custom')], max_length=50),
),
migrations.AlterField(
model_name='formstage',
name='is_predefined',
field=models.BooleanField(default=False, help_text='Whether this is a default resume stage'),
),
migrations.AlterField(
model_name='formstage',
name='name',
field=models.CharField(help_text='Name of the stage', max_length=200),
),
migrations.AlterField(
model_name='formstage',
name='order',
field=models.PositiveIntegerField(default=0, help_text='Order of the stage in the form'),
),
migrations.AlterField(
model_name='formtemplate',
name='created_at',
field=models.DateTimeField(auto_now_add=True, verbose_name='Created at'),
),
migrations.AlterField(
model_name='formtemplate',
name='created_by',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='form_templates', to=settings.AUTH_USER_MODEL),
),
migrations.AlterField(
model_name='formtemplate',
name='description',
field=models.TextField(blank=True, help_text='Description of the form template'),
),
migrations.AlterField(
model_name='formtemplate',
name='is_active',
field=models.BooleanField(default=False, help_text='Whether this template is active'),
),
migrations.AlterField(
model_name='formtemplate',
name='name',
field=models.CharField(help_text='Name of the form template', max_length=200),
),
migrations.AlterField(
model_name='formtemplate',
name='slug',
field=django_extensions.db.fields.RandomCharField(blank=True, editable=False, length=8, unique=True, verbose_name='Slug'),
),
migrations.AlterField(
model_name='formtemplate',
name='updated_at',
field=models.DateTimeField(auto_now=True, verbose_name='Updated at'),
),
migrations.AddIndex(
model_name='formtemplate',
index=models.Index(fields=['created_at'], name='recruitment_created_c21775_idx'),
),
migrations.AddIndex(
model_name='formtemplate',
index=models.Index(fields=['is_active'], name='recruitment_is_acti_ae5efb_idx'),
),
migrations.RemoveField(
model_name='formfield',
name='help_text',
),
migrations.RemoveField(
model_name='formfield',
name='max_date',
),
migrations.RemoveField(
model_name='formfield',
name='min_date',
),
]

View File

@ -1,36 +0,0 @@
# Generated by Django 6.0 on 2025-12-11 12:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0003_alter_formfield_options_alter_formstage_options_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='formfield',
options={'ordering': ['order']},
),
migrations.RemoveField(
model_name='formfield',
name='is_required',
),
migrations.AlterField(
model_name='formfield',
name='field_type',
field=models.CharField(choices=[('text', 'Text Input'), ('number', 'Number Input'), ('email', 'Email'), ('phone', 'Phone'), ('textarea', 'Text Area'), ('file', 'File Upload'), ('date', 'Date Picker'), ('select', 'Dropdown'), ('radio', 'Radio Buttons'), ('checkbox', 'Checkboxes')], help_text='Type of the field', max_length=20),
),
migrations.AlterField(
model_name='formfield',
name='max_value',
field=models.CharField(blank=True, help_text='Max value/date for Number/Date fields', max_length=50),
),
migrations.AlterField(
model_name='formfield',
name='min_value',
field=models.CharField(blank=True, help_text='Min value/date for Number/Date fields', max_length=50),
),
]

View File

@ -1,37 +0,0 @@
# Generated by Django 6.0 on 2025-12-11 13:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('recruitment', '0004_alter_formfield_options_remove_formfield_is_required_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='formfield',
options={'ordering': ['order'], 'verbose_name': 'Form Field', 'verbose_name_plural': 'Form Fields'},
),
migrations.AddField(
model_name='formfield',
name='is_required',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='formfield',
name='field_type',
field=models.CharField(choices=[('text', 'Text Input'), ('email', 'Email'), ('phone', 'Phone'), ('textarea', 'Text Area'), ('file', 'File Upload'), ('date', 'Date Picker'), ('select', 'Dropdown'), ('radio', 'Radio Buttons'), ('checkbox', 'Checkboxes')], help_text='Type of the field', max_length=20),
),
migrations.AlterField(
model_name='formfield',
name='max_value',
field=models.CharField(blank=True, max_length=50),
),
migrations.AlterField(
model_name='formfield',
name='min_value',
field=models.CharField(blank=True, max_length=50),
),
]

View File

@ -187,6 +187,7 @@ class PersonListView(StaffRequiredMixin, ListView, LoginRequiredMixin):
def get_queryset(self):
queryset = super().get_queryset().select_related("user")
search_query = self.request.GET.get("search", "")
print(Person.objects.first().last_name)
if search_query:
queryset=queryset.filter(
Q(first_name=search_query) |
@ -225,22 +226,26 @@ class PersonCreateView(CreateView, LoginRequiredMixin, StaffOrAgencyRequiredMixi
template_name = "people/create_person.html"
form_class = PersonForm
success_url = reverse_lazy("person_list")
print("from agency")
def form_valid(self, form):
if "HX-Request" in self.request.headers:
instance = form.save()
view = self.request.POST.get("view")
if view == "portal":
slug = self.request.POST.get("agency")
if slug:
agency = HiringAgency.objects.get(slug=slug)
print(agency)
instance.agency = agency
instance.save()
return redirect("agency_portal_persons_list")
if view == "job":
return redirect("application_create")
instance = form.save()
view = self.request.POST.get("view")
if view == "portal":
slug = self.request.POST.get("agency")
if slug:
agency = HiringAgency.objects.get(slug=slug)
print(agency)
instance.agency = agency
instance.save()
# 2. Add the content to update (e.g., re-render the person list table)
# response.content = render_to_string('recruitment/persons_table.html',
return redirect("agency_portal_persons_list")
if view == "job":
return redirect("application_create")
return super().form_valid(form)
@ -3114,7 +3119,7 @@ def agency_portal_persons_list(request):
| Q(last_name__icontains=search_query)
| Q(email__icontains=search_query)
| Q(phone=search_query)
| Q(job__title__icontains=search_query)
)
paginator = Paginator(persons, 20) # Show 20 persons per page
@ -3636,7 +3641,7 @@ def message_create(request):
if message.recipient and message.recipient.email:
if request.user.user_type != "staff":
message = message.content
body = message.content
else:
body = (
message.content
@ -5386,19 +5391,19 @@ class ApplicationListView(LoginRequiredMixin, StaffRequiredMixin, ListView):
template_name = "recruitment/applications_list.html"
context_object_name = "applications"
paginate_by = 100
def get_queryset(self):
queryset = (
super()
.get_queryset()
.select_related("person", "job")
.prefetch_related("interview_set")
)
# Handle search
search_query = self.request.GET.get("search", "")
job = self.request.GET.get("job", "")
stage = self.request.GET.get("stage", "")
if search_query:
queryset = queryset.filter(
Q(person__first_name=search_query) |
@ -5463,7 +5468,7 @@ class ApplicationCreateView(
nationalities = cache.get(cache_key)
if nationalities is None:
nationalities = list(
self.model.objects.values_list("nationality", flat=True)
Person.objects.values_list("nationality", flat=True)
.filter(nationality__isnull=False)
.distinct()
.order_by("nationality")
@ -5474,6 +5479,7 @@ class ApplicationCreateView(
context["nationality"] = nationality
context["nationalities"] = nationalities
context["search_query"] = self.request.GET.get("search", "")
context["person_form"]=PersonForm()
return context

View File

@ -83,6 +83,7 @@
.bg-ACTIVE { background-color: var(--kaauh-teal) !important; }
.bg-CLOSED { background-color: var(--kaauh-danger) !important; }
.bg-ARCHIVED { background-color: #343a40 !important; }
.bg-CANCELLED{background-color: red !important; }
/* --- TABLE STYLING --- */
.table {
@ -238,6 +239,7 @@
<option value="DRAFT" {% if status_filter == 'DRAFT' %}selected{% endif %}>{% trans "Draft" %}</option>
<option value="ACTIVE" {% if status_filter == 'ACTIVE' %}selected{% endif %}>{% trans "Active" %}</option>
<option value="CLOSED" {% if status_filter == 'CLOSED' %}selected{% endif %}>{% trans "Closed" %}</option>
<option value="CANCELLED" {% if status_filter == 'CANCELLED' %}selected{% endif %}>{% trans "Cancelled" %}</option>
<option value="ARCHIVED" {% if status_filter == 'ARCHIVED' %}selected{% endif %}>{% trans "Archived" %}</option>
</select>
</div>

View File

@ -64,7 +64,7 @@
<div class="col-md-3 mb-2">
<div class="kaauh-card shadow-sm h-100">
<div class="card-body text-center px-2 py-2">
<div class="text-primary mb-2">
<div class="text-primary-theme mb-2">
<i class="fas fa-briefcase fa-2x"></i>
</div>
<h4 class="card-title">{{ total_assignments }}</h4>
@ -75,7 +75,7 @@
<div class="col-md-3 mb-2">
<div class="kaauh-card shadow-sm h-100 px-2 py-2">
<div class="card-body text-center">
<div class="text-success mb-2">
<div class="text-primary-theme mb-2">
<i class="fas fa-check-circle fa-2x"></i>
</div>
<h4 class="card-title">{{ active_assignments }}</h4>
@ -86,7 +86,7 @@
<div class="col-md-3 mb-2">
<div class="kaauh-card shadow-sm h-100 px-2 py-2">
<div class="card-body text-center">
<div class="text-info mb-2">
<div class="text-primary-theme mb-2">
<i class="fas fa-users fa-2x"></i>
</div>
<h4 class="card-title">{{ total_applications }}</h4>
@ -166,7 +166,7 @@
</div>
<div class="col-6">
<small class="text-muted d-block">{% trans "Applications" %}</small>
<strong>{{ stats.application_count }} / {{ stats.assignment.max_applications }}</strong>
<strong>{{ stats.application_count }} / {{ stats.assignment.max_candidates}}</strong>
</div>
</div>
@ -174,7 +174,7 @@
<div class="mb-3">
<div class="d-flex justify-content-between mb-1">
<small class="text-muted">{% trans "Submission Progress" %}</small>
<small class="text-muted">{{ stats.application_count }}/{{ stats.assignment.max_applications }}</small>
<small class="text-muted">{{ stats.application_count }}/{{ stats.assignment.max_candidates }}</small>
</div>
<div class="progress" style="height: 6px;">
{% with progress=stats.application_count %}

View File

@ -91,7 +91,7 @@
id="search"
name="q"
value="{{ search_query }}"
placeholder="{% trans 'Search by name, email, phone, or job title...' %}">
placeholder="{% trans 'Search by name, email, phone...' %}">
</div>
{% comment %} <div class="col-md-3">
<label for="stage" class="form-label fw-semibold">
@ -122,7 +122,7 @@
<div class="col-md-6">
<div class="kaauh-card shadow-sm h-100 p-3">
<div class="card-body text-center">
<div class="text-info mb-2">
<div class="text-primary-theme mb-2">
<i class="fas fa-users fa-2x"></i>
</div>
<h4 class="card-title">{{ total_persons }}</h4>
@ -133,7 +133,7 @@
<div class="col-md-6">
<div class="kaauh-card shadow-sm h-100 p-3">
<div class="card-body text-center">
<div class="text-success mb-2">
<div class="text-primary-theme mb-2">
<i class="fas fa-check-circle fa-2x"></i>
</div>
<h4 class="card-title">{{ page_obj|length }}</h4>
@ -324,10 +324,12 @@
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="personModalBody">
<form id="person_form" hx-post="{% url 'person_create' %}" hx-vals='{"view":"portal","agency":"{{ agency.slug }}"}' hx-select=".persons-list" hx-target=".persons-list" hx-swap="outerHTML"
hx-on:afterRequest="$('#personModal').modal('hide')">
<form id="person_form" method="post" action="{% url 'person_create' %}" >
{% csrf_token %}
<input type="hidden" name="view" value="portal">
<input type="hidden" name="agency" value="{{ agency.slug }}">
<div class="row g-4">
<div class="col-md-4">
{{ person_form.first_name|as_crispy_field }}
@ -342,6 +344,7 @@
<div class="row g-4">
<div class="col-md-6">
{{ person_form.email|as_crispy_field }}
{{person_form.errors}}
</div>
<div class="col-md-6">
{{ person_form.phone|as_crispy_field }}

View File

@ -98,7 +98,7 @@
<div class="d-flex gap-2 mt-1">
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#personModal">
<i class="fas fa-user-plus me-1"></i>
<span class="d-none d-sm-inline">{% trans "Create New Person" %}</span>
<span class="d-none d-sm-inline">{% trans "Create New Applicant" %}</span>
</button>
<a href="{% url 'application_list' %}" class="btn btn-outline-light btn-sm" title="{% trans 'Back to List' %}">
<i class="fas fa-arrow-left"></i>
@ -171,6 +171,14 @@
{{ person_form.phone|as_crispy_field }}
</div>
</div>
<div class="row g-4">
<div class="col-md-6">
{{ person_form.gpa|as_crispy_field }}
</div>
<div class="col-md-6">
{{ person_form.national_id|as_crispy_field }}
</div>
</div>
<div class="row g-4">
<div class="col-md-6">
{{ person_form.date_of_birth|as_crispy_field }}

View File

@ -5,6 +5,15 @@
{% block content %}
<div class="container-fluid">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{% url 'source_list' %}" class="text-decoration-none text-secondary">{% trans "Souce Settings" %}</a></li>
<li class="breadcrumb-item active" aria-current="page" style="
color: #F43B5E; /* Rosy Accent Color */
font-weight: 600;
">{% trans "Source Detail" %}</li>
</ol>
</nav>
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">

View File

@ -45,7 +45,7 @@
{% endfor %}
{% endif %}
<form method="post" class="space-y-4">
<form method="post" class="space-y-4">
{% csrf_token %}
{{ form|crispy }}

View File

@ -112,7 +112,13 @@
<p class="text-muted mb-0">{% trans "Manage your personal details and security." %}</p>
</div>
<div class="rounded-circle bg-primary-subtle text-accent d-flex align-items-center justify-content-center" style="width: 50px; height: 50px; font-size: 1.5rem;">
{% if user.first_name %}{{ user.first_name.0 }}{% else %}<i class="fas fa-user"></i>{% endif %}
{% if user.profile_image %}
<img src="{{ user.profile_image.url }}" alt="{{ user.username }}" class="profile-avatar"
style="width: 100px; height: 100px; object-fit: cover; background-color: var(--kaauh-teal); display: inline-block; vertical-align: middle;"
title="{% trans 'Your account' %}">
{% else %}
{% if user.first_name %}{{ user.first_name.0 }}{% else %}<i class="fas fa-user"></i>{% endif %}
{% endif %}
</div>
</div>