281 lines
16 KiB
HTML
281 lines
16 KiB
HTML
{% extends 'portal_base.html' %}
|
|
{% load static i18n crispy_forms_tags %}
|
|
|
|
{% block title %}{% trans "Agency Applicant List" %} - ATS{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container mx-auto px-4 py-8">
|
|
<!-- Header -->
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6 px-2 py-2">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
|
|
<div class="bg-temple-red/10 p-3 rounded-xl">
|
|
<i data-lucide="users" class="w-8 h-8 text-temple-red"></i>
|
|
</div>
|
|
{% trans "All Applicants" %}
|
|
</h1>
|
|
<p class="text-gray-600">{% trans "All applicants who come through" %} {{ agency.name }}</p>
|
|
</div>
|
|
<button type="button" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2.5 rounded-xl transition shadow-sm hover:shadow-md" data-bs-toggle="modal" data-bs-target="#personModal">
|
|
<i data-lucide="plus" class="w-4 h-4"></i> {% trans "Add New Applicant" %}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Search and Filter Section -->
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden mb-6">
|
|
<div class="p-6">
|
|
<form method="get">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label for="search" class="block text-sm font-semibold text-gray-700 mb-2 flex items-center gap-2">
|
|
<i data-lucide="search" class="w-4 h-4"></i>{% trans "Search" %}
|
|
</label>
|
|
<input type="text"
|
|
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-temple-red focus:border-transparent transition"
|
|
id="search"
|
|
name="q"
|
|
value="{{ search_query }}"
|
|
placeholder="{% trans 'Search by name, email, phone...' %}">
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Results Summary -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<div class="p-6 text-center">
|
|
<div class="w-12 h-12 bg-temple-red/10 rounded-lg flex items-center justify-center mx-auto mb-3">
|
|
<i data-lucide="users" class="w-6 h-6 text-temple-red"></i>
|
|
</div>
|
|
<h4 class="text-2xl font-bold text-gray-900 mb-1">{{ total_persons }}</h4>
|
|
<p class="text-gray-600">{% trans "Total Persons" %}</p>
|
|
</div>
|
|
</div>
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<div class="p-6 text-center">
|
|
<div class="w-12 h-12 bg-temple-red/10 rounded-lg flex items-center justify-center mx-auto mb-3">
|
|
<i data-lucide="check-circle" class="w-6 h-6 text-temple-red"></i>
|
|
</div>
|
|
<h4 class="text-2xl font-bold text-gray-900 mb-1">{{ page_obj|length }}</h4>
|
|
<p class="text-gray-600">{% trans "Showing on this page" %}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Persons Table -->
|
|
<div class="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<div class="overflow-x-auto">
|
|
{% if page_obj %}
|
|
<table class="w-full">
|
|
<thead class="bg-gray-50 border-b border-gray-200">
|
|
<tr>
|
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Name" %}</th>
|
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Email" %}</th>
|
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Phone" %}</th>
|
|
<th class="text-left py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Created At" %}</th>
|
|
<th class="text-center py-4 px-4 text-sm font-semibold text-gray-700">{% trans "Actions" %}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for person in page_obj %}
|
|
<tr class="border-b border-gray-100 hover:bg-gray-50 transition cursor-pointer">
|
|
<td class="py-4 px-4">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 bg-temple-red text-white rounded-lg flex items-center justify-center font-bold text-sm">
|
|
{{ person.first_name|first|upper }}{{ person.last_name|first|upper }}
|
|
</div>
|
|
<div>
|
|
<div class="font-semibold text-gray-900">{{ person.first_name }} {{ person.last_name }}</div>
|
|
{% if person.address %}
|
|
<div class="text-sm text-gray-500">{{ person.address|truncatechars:50 }}</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="py-4 px-4">
|
|
<a href="mailto:{{ person.email }}" class="text-gray-900 hover:text-temple-red transition">{{ person.email }}</a>
|
|
</td>
|
|
<td class="py-4 px-4">{{ person.phone|default:"-" }}</td>
|
|
<td class="py-4 px-4">{{ person.created_at|date:"d-m-Y" }}</td>
|
|
<td class="py-4 px-4 text-center">
|
|
<button type="button" data-bs-toggle="modal" data-bs-target="#updateModal"
|
|
hx-get="{% url 'person_update' person.slug %}"
|
|
hx-target="#updateModalBody"
|
|
hx-swap="outerrHTML"
|
|
hx-select="#person-form"
|
|
hx-vals='{"view":"portal"}'
|
|
class="inline-flex items-center justify-center w-9 h-9 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition"
|
|
title="{% trans 'Edit Person' %}">
|
|
<i data-lucide="edit-2" class="w-4 h-4"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<div class="text-center py-10">
|
|
<div class="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<i data-lucide="users" class="w-8 h-8 text-gray-400"></i>
|
|
</div>
|
|
<h5 class="text-gray-600 mb-2">{% trans "No persons found" %}</h5>
|
|
<p class="text-gray-500 mb-4">
|
|
{% if search_query or stage_filter %}
|
|
{% trans "Try adjusting your search or filter criteria." %}
|
|
{% else %}
|
|
{% trans "No persons have been added yet." %}
|
|
{% endif %}
|
|
</p>
|
|
{% if not search_query and not stage_filter and agency.assignments.exists %}
|
|
<a href="{% url 'agency_portal_submit_application_page' agency.assignments.first.slug %}"
|
|
class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-6 py-2.5 rounded-xl transition shadow-sm hover:shadow-md">
|
|
<i data-lucide="user-plus" class="w-4 h-4"></i> {% trans "Add First Person" %}
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if page_obj.has_other_pages %}
|
|
<nav aria-label="{% trans 'Persons pagination' %}" class="mt-6">
|
|
<div class="flex justify-center items-center gap-2">
|
|
{% if page_obj.has_previous %}
|
|
<a href="?page=1{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
|
<i data-lucide="chevrons-left" class="w-4 h-4"></i>
|
|
</a>
|
|
<a href="?page={{ page_obj.previous_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
|
<i data-lucide="chevron-left" class="w-4 h-4"></i>
|
|
</a>
|
|
{% endif %}
|
|
|
|
{% for num in page_obj.paginator.page_range %}
|
|
{% if page_obj.number == num %}
|
|
<span class="inline-flex items-center justify-center w-10 h-10 rounded-lg bg-temple-red text-white font-semibold">{{ num }}</span>
|
|
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
|
|
<a href="?page={{ num }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">{{ num }}</a>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if page_obj.has_next %}
|
|
<a href="?page={{ page_obj.next_page_number }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
|
<i data-lucide="chevron-right" class="w-4 h-4"></i>
|
|
</a>
|
|
<a href="?page={{ page_obj.paginator.num_pages }}{% if search_query %}&q={{ search_query }}{% endif %}{% if stage_filter %}&stage={{ stage_filter }}{% endif %}" class="inline-flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:border-temple-red hover:text-temple-red text-gray-600 transition">
|
|
<i data-lucide="chevrons-right" class="w-4 h-4"></i>
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</nav>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Empty Modal -->
|
|
<div class="modal fade" id="updateModal" tabindex="-1" aria-labelledby="updateModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content" hx-boost="true" hx-vals='{"view":"portal"}' hx-select=".person-table" hx-target=".person-table"
|
|
hx-swap="outerHTML" hx-on::after-request="closeOpenBootstrapModal()">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="updateModalLabel">{% trans "Update" %}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" id="updateModalBody">
|
|
</div>
|
|
<div class="modal-footer">
|
|
<div class="flex gap-2">
|
|
<button form="person-form" type="submit" class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition">
|
|
<i data-lucide="save" class="w-4 h-4"></i> {% trans "Update" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Person Modal -->
|
|
<div class="modal fade" id="personModal" tabindex="-1" aria-labelledby="personModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog modal-lg">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title flex items-center gap-2" id="personModalLabel">
|
|
<i data-lucide="users" class="w-5 h-5"></i>
|
|
{% trans "Applicant Details" %}
|
|
</h5>
|
|
<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" 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="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div>
|
|
{{ person_form.first_name|as_crispy_field }}
|
|
</div>
|
|
<div>
|
|
{{ person_form.middle_name|as_crispy_field }}
|
|
</div>
|
|
<div>
|
|
{{ person_form.last_name|as_crispy_field }}
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
{{ person_form.email|as_crispy_field }}
|
|
{{person_form.errors}}
|
|
</div>
|
|
<div>
|
|
{{ person_form.phone|as_crispy_field }}
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
{{ person_form.gpa|as_crispy_field }}
|
|
</div>
|
|
<div>
|
|
{{ person_form.national_id|as_crispy_field }}
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
{{ person_form.date_of_birth|as_crispy_field }}
|
|
</div>
|
|
<div>
|
|
{{ person_form.nationality|as_crispy_field }}
|
|
</div>
|
|
</div>
|
|
<div>
|
|
{{ person_form.address|as_crispy_field }}
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="inline-flex items-center gap-2 bg-temple-red hover:bg-[#7a1a29] text-white font-semibold px-4 py-2 rounded-xl transition" type="submit" form="person_form">{% trans "Save" %}</button>
|
|
<button type="button" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 font-medium px-4 py-2 rounded-xl transition" data-bs-dismiss="modal">
|
|
{% trans "Close" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function openPersonModal(personId, personName) {
|
|
const modal = new bootstrap.Modal(document.getElementById('personModal'));
|
|
document.getElementById('person-modal-text').innerHTML = `<strong>${personName}</strong> (ID: ${personId})`;
|
|
modal.show();
|
|
}
|
|
|
|
function editPerson(personId) {
|
|
console.log('Edit person:', personId);
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
lucide.createIcons();
|
|
});
|
|
</script>
|
|
{% endblock %} |