231 lines
10 KiB
HTML
231 lines
10 KiB
HTML
{% extends "base.html" %}
|
|
{% load static i18n crispy_forms_tags %}
|
|
|
|
{% block title %}{% trans "Update" %} {{ object.name }} - {{ block.super }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="px-4 py-6 max-w-4xl mx-auto">
|
|
<!-- Breadcrumb Navigation -->
|
|
<nav aria-label="breadcrumb" class="mb-6">
|
|
<ol class="flex items-center space-x-2 text-sm">
|
|
<li>
|
|
<a href="{% url 'application_list' %}" class="text-gray-500 hover:text-temple-red transition">
|
|
<i data-lucide="users" class="w-4 h-4 inline mr-1"></i> {% trans "Applications" %}
|
|
</a>
|
|
</li>
|
|
<li class="text-gray-400">/</li>
|
|
<li>
|
|
<a href="{% url 'application_detail' object.slug %}" class="text-gray-500 hover:text-temple-red transition">
|
|
{{ object.name }}
|
|
</a>
|
|
</li>
|
|
<li class="text-temple-red font-semibold" aria-current="page">{% trans "Update" %}</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<!-- Header -->
|
|
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-6">
|
|
<h3 class="text-2xl font-bold text-temple-dark flex items-center gap-2">
|
|
<i data-lucide="user-edit" class="w-6 h-6"></i>
|
|
{% trans "Update Application" %}
|
|
</h3>
|
|
<div class="flex gap-2">
|
|
<a href="{% url 'application_detail' object.slug %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
|
|
<i data-lucide="eye" class="w-4 h-4"></i>
|
|
{% trans "View Details" %}
|
|
</a>
|
|
<a href="{% url 'application_delete' object.slug %}" class="inline-flex items-center gap-2 bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition">
|
|
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
|
{% trans "Delete" %}
|
|
</a>
|
|
<a href="{% url 'application_list' %}" class="inline-flex items-center gap-2 border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg text-sm font-medium transition">
|
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
|
{% trans "Back to List" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Current Profile Info -->
|
|
<div class="bg-white rounded-xl shadow-md border border-gray-200 mb-6">
|
|
<div class="p-6">
|
|
<div class="bg-gray-50 rounded-lg p-4">
|
|
<h6 class="text-sm font-semibold text-temple-dark mb-3 flex items-center gap-2">
|
|
<i data-lucide="info" class="w-4 h-4"></i>
|
|
{% trans "Currently Editing" %}
|
|
</h6>
|
|
<div class="flex items-center gap-4">
|
|
{% if object.profile_image %}
|
|
<img src="{{ object.profile_image.url }}" alt="{{ object.name }}"
|
|
class="w-24 h-24 rounded-full border-2 border-temple-red object-cover">
|
|
{% else %}
|
|
<div class="w-24 h-24 rounded-full border-2 border-gray-300 bg-gray-100 flex items-center justify-center">
|
|
<i data-lucide="user" class="w-8 h-8 text-gray-400"></i>
|
|
</div>
|
|
{% endif %}
|
|
<div>
|
|
<h5 class="text-lg font-semibold text-gray-900 mb-1">{{ object.name }}</h5>
|
|
{% if object.email %}
|
|
<p class="text-gray-600 text-sm mb-1">{{ object.email }}</p>
|
|
{% endif %}
|
|
<p class="text-gray-500 text-xs">
|
|
{% trans "Created" %}: {{ object.created_at|date:"d M Y" }} •
|
|
{% trans "Last Updated" %}: {{ object.updated_at|date:"d M Y" }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Card -->
|
|
<div class="bg-white rounded-xl shadow-md border border-gray-200">
|
|
<div class="p-6">
|
|
{% if form.non_field_errors %}
|
|
<div class="bg-red-50 border border-red-200 rounded-lg p-4 mb-4" role="alert">
|
|
<h5 class="font-semibold text-red-800 flex items-center gap-2 mb-2">
|
|
<i data-lucide="alert-triangle" class="w-5 h-5"></i>
|
|
{% trans "Error" %}
|
|
</h5>
|
|
{% for error in form.non_field_errors %}
|
|
<p class="text-red-700 text-sm">{{ error }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if messages %}
|
|
{% for message in messages %}
|
|
<div class="rounded-lg p-4 mb-4 {% if message.tags == 'success' %}bg-green-50 border border-green-200 text-green-800{% elif message.tags == 'error' %}bg-red-50 border border-red-200 text-red-800{% else %}bg-blue-50 border border-blue-200 text-blue-800{% endif %}" role="alert">
|
|
{{ message }}
|
|
</div>
|
|
{% endfor %}
|
|
{% endif %}
|
|
|
|
<form method="post" action="{% url 'application_update' object.slug %}" enctype="multipart/form-data" id="candidate-form">
|
|
{% csrf_token %}
|
|
{{ form|crispy }}
|
|
</form>
|
|
|
|
<div class="flex gap-2 mt-6">
|
|
<button form="candidate-form" type="submit" class="bg-temple-red hover:bg-red-800 text-white font-semibold px-8 py-3 rounded-xl transition shadow-md hover:shadow-lg flex items-center gap-2">
|
|
<i data-lucide="save" class="w-5 h-5"></i>
|
|
{% trans "Update" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block customJS %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize Lucide icons
|
|
lucide.createIcons();
|
|
|
|
// Add form styling
|
|
const formInputs = document.querySelectorAll('input:not([type="hidden"]), select, textarea');
|
|
formInputs.forEach(input => {
|
|
input.classList.add('w-full', 'px-3', 'py-2.5', 'border', 'border-gray-300', 'rounded-lg', 'focus:ring-2', 'focus:ring-temple-red', 'focus:border-transparent', 'transition');
|
|
});
|
|
|
|
// Style form labels and containers
|
|
const formGroups = document.querySelectorAll('.form-group');
|
|
formGroups.forEach(group => {
|
|
group.classList.add('mb-6');
|
|
});
|
|
|
|
const formLabels = document.querySelectorAll('label');
|
|
formLabels.forEach(label => {
|
|
label.classList.add('block', 'text-sm', 'font-semibold', 'text-gray-700', 'mb-2');
|
|
});
|
|
|
|
// Profile Image Preview
|
|
const profileImageInput = document.getElementById('id_profile_image');
|
|
const imagePreviewContainer = document.getElementById('image-preview-container');
|
|
const originalImage = imagePreviewContainer ? imagePreviewContainer.innerHTML : '';
|
|
|
|
if (profileImageInput) {
|
|
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) {
|
|
if (imagePreviewContainer) {
|
|
imagePreviewContainer.innerHTML = `
|
|
<img src="${e.target.result}" alt="Profile Preview" class="w-32 h-32 rounded-full border-3 border-temple-red object-cover mx-auto mb-3">
|
|
<h5 class="text-gray-600 text-sm font-medium">${file.name}</h5>
|
|
<p class="text-gray-500 text-xs">{% trans "New photo selected" %}</p>
|
|
`;
|
|
}
|
|
};
|
|
reader.readAsDataURL(file);
|
|
} else if (!file && imagePreviewContainer) {
|
|
// Reset to original if no file selected
|
|
imagePreviewContainer.innerHTML = originalImage;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Form Validation
|
|
const form = document.getElementById('candidate-form');
|
|
if (form) {
|
|
form.addEventListener('submit', function(e) {
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
|
|
// Add loading state
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = `<i data-lucide="loader-2" class="w-5 h-5 mr-2 animate-spin"></i> {% trans "Saving..." %}`;
|
|
|
|
// Basic validation
|
|
const name = document.getElementById('id_name');
|
|
if (name && !name.value.trim()) {
|
|
e.preventDefault();
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Update" %}`;
|
|
alert('{% trans "Name is required." %}');
|
|
return;
|
|
}
|
|
|
|
const email = document.getElementById('id_email');
|
|
if (email && email.value.trim() && !isValidEmail(email.value.trim())) {
|
|
e.preventDefault();
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = `<i data-lucide="save" class="w-5 h-5 mr-2"></i> {% trans "Update" %}`;
|
|
alert('{% trans "Please enter a valid email address." %}');
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Email validation helper
|
|
function isValidEmail(email) {
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
return emailRegex.test(email);
|
|
}
|
|
|
|
// Warn before leaving if changes are made
|
|
let formChanged = false;
|
|
const formInputsTrack = form ? form.querySelectorAll('input, select, textarea') : [];
|
|
|
|
formInputsTrack.forEach(input => {
|
|
input.addEventListener('change', function() {
|
|
formChanged = true;
|
|
});
|
|
});
|
|
|
|
window.addEventListener('beforeunload', function(e) {
|
|
if (formChanged) {
|
|
e.preventDefault();
|
|
e.returnValue = '{% trans "You have unsaved changes. Are you sure you want to leave?" %}';
|
|
return e.returnValue;
|
|
}
|
|
});
|
|
|
|
if (form) {
|
|
form.addEventListener('submit', function() {
|
|
formChanged = false;
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %} |