463 lines
20 KiB
HTML
463 lines
20 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{% trans "Create Source User" %} - {{ source.name_en }}{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.page-header-gradient {
|
|
background: linear-gradient(135deg, #005696 0%, #0069a8 50%, #007bbd 100%);
|
|
color: white;
|
|
padding: 1.5rem 2rem;
|
|
border-radius: 1rem;
|
|
margin-bottom: 1.5rem;
|
|
box-shadow: 0 10px 15px -3px rgba(0, 86, 150, 0.2);
|
|
}
|
|
|
|
.form-section {
|
|
background: #fff;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 1rem;
|
|
padding: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.form-section:hover {
|
|
border-color: #005696;
|
|
box-shadow: 0 4px 12px rgba(0, 86, 150, 0.1);
|
|
}
|
|
|
|
.form-label {
|
|
display: block;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
color: #1e293b;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.form-control, .form-select {
|
|
width: 100%;
|
|
padding: 0.75rem 1rem;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 0.75rem;
|
|
font-size: 0.875rem;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.form-control:focus, .form-select:focus {
|
|
outline: none;
|
|
border-color: #005696;
|
|
box-shadow: 0 0 0 3px rgba(0, 86, 150, 0.1);
|
|
}
|
|
|
|
.btn-primary {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.75rem 1.5rem;
|
|
background: #005696;
|
|
color: white;
|
|
border-radius: 0.75rem;
|
|
font-weight: 600;
|
|
transition: all 0.2s ease;
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
.btn-primary:hover {
|
|
background: #007bbd;
|
|
}
|
|
|
|
.btn-secondary {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.75rem 1.5rem;
|
|
background: white;
|
|
color: #64748b;
|
|
border: 2px solid #e2e8f0;
|
|
border-radius: 0.75rem;
|
|
font-weight: 600;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.btn-secondary:hover {
|
|
background: #f1f5f9;
|
|
border-color: #005696;
|
|
}
|
|
|
|
.tab-container {
|
|
display: none;
|
|
}
|
|
.tab-container.active {
|
|
display: block;
|
|
}
|
|
.tab-button {
|
|
border-bottom: 2px solid transparent;
|
|
transition: all 0.2s;
|
|
}
|
|
.tab-button.active {
|
|
border-bottom-color: #005696;
|
|
color: #005696;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="p-6">
|
|
<!-- Breadcrumb -->
|
|
<nav class="mb-4">
|
|
<ol class="flex items-center gap-2 text-sm text-slate">
|
|
<li><a href="{% url 'px_sources:source_list' %}" class="text-blue hover:text-navy font-medium">{% trans "PX Sources" %}</a></li>
|
|
<li><i data-lucide="chevron-right" class="w-4 h-4"></i></li>
|
|
<li><a href="{% url 'px_sources:source_detail' source.pk %}" class="text-blue hover:text-navy font-medium">{{ source.name_en }}</a></li>
|
|
<li><i data-lucide="chevron-right" class="w-4 h-4"></i></li>
|
|
<li class="text-navy font-semibold">{% trans "Create Source User" %}</li>
|
|
</ol>
|
|
</nav>
|
|
|
|
<!-- Page Header -->
|
|
<div class="page-header-gradient">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<h1 class="text-2xl font-bold flex items-center gap-3">
|
|
<i data-lucide="user-plus" class="w-6 h-6"></i>
|
|
{% trans "Create Source User" %}
|
|
</h1>
|
|
<p class="text-blue-100 text-sm mt-1">{{ source.name_en }}</p>
|
|
</div>
|
|
<a href="{% url 'px_sources:source_detail' source.pk %}" class="btn-secondary bg-white/10 border-white/30 text-white hover:bg-white hover:text-navy">
|
|
<i data-lucide="arrow-left" class="w-4 h-4"></i>
|
|
{% trans "Back to Source" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Section -->
|
|
<div class="max-w-4xl">
|
|
<div class="form-section p-0 overflow-hidden">
|
|
<!-- Tabs -->
|
|
<div class="border-b border-slate-200 bg-slate-50/50">
|
|
<div class="flex gap-6 px-6 pt-4">
|
|
<button type="button" onclick="switchTab('existing')" id="tab-existing"
|
|
class="tab-button active pb-3 px-2 font-semibold text-slate-600 hover:text-navy transition">
|
|
<i data-lucide="users" class="w-5 h-5 inline mr-2"></i>
|
|
{% trans "Select Existing User" %}
|
|
</button>
|
|
<button type="button" onclick="switchTab('new')" id="tab-new"
|
|
class="tab-button pb-3 px-2 font-semibold text-slate-600 hover:text-navy transition">
|
|
<i data-lucide="user-plus" class="w-5 h-5 inline mr-2"></i>
|
|
{% trans "Create New User" %}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="p-6">
|
|
<form method="POST" novalidate id="sourceUserForm">
|
|
{% csrf_token %}
|
|
|
|
<!-- Creation Mode Hidden Field -->
|
|
<input type="hidden" name="creation_mode" id="creationMode" value="existing">
|
|
|
|
<!-- Tab 1: Select Existing User -->
|
|
<div id="tab-existing-content" class="tab-container active">
|
|
<div class="mb-6">
|
|
<label for="id_user" class="form-label">
|
|
{% trans "Select User" %} <span class="text-red-500">*</span>
|
|
</label>
|
|
<select name="user" id="id_user" class="form-control">
|
|
<option value="">{% trans "Choose a user..." %}</option>
|
|
{% for user in available_users %}
|
|
<option value="{{ user.id }}">
|
|
{{ user.email }}
|
|
{% if user.get_full_name %}
|
|
- {{ user.get_full_name }}
|
|
{% endif %}
|
|
{% if user.groups.first %}
|
|
({{ user.groups.first.name }})
|
|
{% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
<p class="text-slate text-sm mt-2">
|
|
<i data-lucide="info" class="w-4 h-4 inline mr-1"></i>
|
|
{% trans "Select an existing user to assign as source user. A user can only manage one source." %}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Info Box -->
|
|
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
|
<div class="flex items-start gap-3">
|
|
<i data-lucide="lightbulb" class="w-5 h-5 text-blue-600 mt-0.5"></i>
|
|
<div>
|
|
<p class="text-sm font-semibold text-blue-800 mb-1">{% trans "Quick Tip" %}</p>
|
|
<p class="text-sm text-blue-700">
|
|
{% trans "If you need to create a new user account first, switch to the 'Create New User' tab above." %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tab 2: Create New User -->
|
|
<div id="tab-new-content" class="tab-container">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-5 mb-6">
|
|
<!-- Email -->
|
|
<div class="md:col-span-2">
|
|
<label for="new_email" class="form-label">
|
|
{% trans "Email Address" %} <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="email" name="new_email" id="new_email" required
|
|
class="form-control"
|
|
placeholder="user@example.com">
|
|
<p class="text-xs text-slate mt-1">
|
|
{% trans "This will be the user's login email" %}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- First Name -->
|
|
<div>
|
|
<label for="new_first_name" class="form-label">
|
|
{% trans "First Name" %} <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" name="new_first_name" id="new_first_name" required
|
|
class="form-control"
|
|
placeholder="{% trans 'John' %}">
|
|
</div>
|
|
|
|
<!-- Last Name -->
|
|
<div>
|
|
<label for="new_last_name" class="form-label">
|
|
{% trans "Last Name" %} <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" name="new_last_name" id="new_last_name" required
|
|
class="form-control"
|
|
placeholder="{% trans 'Doe' %}">
|
|
</div>
|
|
|
|
<!-- Phone -->
|
|
<div>
|
|
<label for="new_phone" class="form-label">
|
|
{% trans "Phone Number" %}
|
|
</label>
|
|
<input type="tel" name="new_phone" id="new_phone"
|
|
class="form-control"
|
|
placeholder="+966 XX XXX XXXX">
|
|
</div>
|
|
|
|
<!-- Employee ID -->
|
|
<div>
|
|
<label for="new_employee_id" class="form-label">
|
|
{% trans "Employee ID" %}
|
|
</label>
|
|
<input type="text" name="new_employee_id" id="new_employee_id"
|
|
class="form-control"
|
|
placeholder="{% trans 'Optional' %}">
|
|
</div>
|
|
|
|
<!-- Password -->
|
|
<div>
|
|
<label for="new_password" class="form-label">
|
|
{% trans "Password" %} <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="password" name="new_password" id="new_password" required minlength="8"
|
|
class="form-control"
|
|
placeholder="••••••••">
|
|
<p class="text-xs text-slate mt-1">
|
|
{% trans "Minimum 8 characters" %}
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Confirm Password -->
|
|
<div>
|
|
<label for="new_password_confirm" class="form-label">
|
|
{% trans "Confirm Password" %} <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="password" name="new_password_confirm" id="new_password_confirm" required minlength="8"
|
|
class="form-control"
|
|
placeholder="••••••••">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info Box -->
|
|
<div class="bg-emerald-50 border border-emerald-200 rounded-lg p-4 mb-6">
|
|
<div class="flex items-start gap-3">
|
|
<i data-lucide="shield-check" class="w-5 h-5 text-emerald-600 mt-0.5"></i>
|
|
<div>
|
|
<p class="text-sm font-semibold text-emerald-800 mb-1">{% trans "Auto-Assignment" %}</p>
|
|
<p class="text-sm text-emerald-700">
|
|
{% trans "The new user will be automatically assigned the PX Admin role and linked to this source." %}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border-t border-slate-200 my-6"></div>
|
|
|
|
<!-- Status -->
|
|
<div class="mb-6">
|
|
<label class="form-label">{% trans "Status" %}</label>
|
|
<label class="flex items-center gap-3 cursor-pointer">
|
|
<input type="checkbox" name="is_active" id="id_is_active" checked
|
|
class="w-5 h-5 text-navy border-slate-300 rounded focus:ring-blue">
|
|
<span class="text-navy font-medium">{% trans "Active" %}</span>
|
|
</label>
|
|
<p class="text-slate text-sm mt-1">
|
|
{% trans "Inactive users will not be able to access their dashboard." %}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="border-t border-slate-200 my-6"></div>
|
|
|
|
<!-- Permissions -->
|
|
<h5 class="text-lg font-semibold text-navy mb-4">{% trans "Permissions" %}</h5>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
|
|
<div>
|
|
<label class="flex items-center gap-3 cursor-pointer">
|
|
<input type="checkbox" name="can_create_complaints" id="id_can_create_complaints" checked
|
|
class="w-5 h-5 text-navy border-slate-300 rounded focus:ring-blue">
|
|
<span class="text-navy">{% trans "Can create complaints" %}</span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<label class="flex items-center gap-3 cursor-pointer">
|
|
<input type="checkbox" name="can_create_inquiries" id="id_can_create_inquiries" checked
|
|
class="w-5 h-5 text-navy border-slate-300 rounded focus:ring-blue">
|
|
<span class="text-navy">{% trans "Can create inquiries" %}</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Info Alert -->
|
|
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
|
<div class="flex items-center gap-3">
|
|
<i data-lucide="info" class="w-5 h-5 text-blue-600"></i>
|
|
<span class="text-blue-700 text-sm">{% trans "Permissions control what the source user can do in their dashboard. Uncheck to restrict access." %}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Submit Buttons -->
|
|
<div class="flex gap-3 pt-4 border-t border-slate-200">
|
|
<button type="submit" class="btn-primary">
|
|
<i data-lucide="check" class="w-4 h-4"></i>
|
|
{% trans "Create Source User" %}
|
|
</button>
|
|
<a href="{% url 'px_sources:source_detail' source.pk %}" class="btn-secondary">
|
|
<i data-lucide="x" class="w-4 h-4"></i>
|
|
{% trans "Cancel" %}
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
lucide.createIcons();
|
|
|
|
// Form validation
|
|
const form = document.getElementById('sourceUserForm');
|
|
form.addEventListener('submit', function(e) {
|
|
const mode = document.getElementById('creationMode').value;
|
|
|
|
if (mode === 'existing') {
|
|
const userSelect = document.getElementById('id_user');
|
|
if (!userSelect.value) {
|
|
e.preventDefault();
|
|
showAlert('error', '{% trans "Please select a user" %}');
|
|
userSelect.focus();
|
|
return false;
|
|
}
|
|
} else {
|
|
// Validate new user fields
|
|
const email = document.getElementById('new_email').value;
|
|
const firstName = document.getElementById('new_first_name').value;
|
|
const lastName = document.getElementById('new_last_name').value;
|
|
const password = document.getElementById('new_password').value;
|
|
const passwordConfirm = document.getElementById('new_password_confirm').value;
|
|
|
|
if (!email || !firstName || !lastName || !password) {
|
|
e.preventDefault();
|
|
showAlert('error', '{% trans "Please fill in all required fields" %}');
|
|
return false;
|
|
}
|
|
|
|
if (password.length < 8) {
|
|
e.preventDefault();
|
|
showAlert('error', '{% trans "Password must be at least 8 characters" %}');
|
|
document.getElementById('new_password').focus();
|
|
return false;
|
|
}
|
|
|
|
if (password !== passwordConfirm) {
|
|
e.preventDefault();
|
|
showAlert('error', '{% trans "Passwords do not match" %}');
|
|
document.getElementById('new_password_confirm').focus();
|
|
return false;
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
function switchTab(tab) {
|
|
// Update hidden field
|
|
document.getElementById('creationMode').value = tab;
|
|
|
|
// Update tab buttons
|
|
document.querySelectorAll('.tab-button').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
document.getElementById('tab-' + tab).classList.add('active');
|
|
|
|
// Update tab content
|
|
document.querySelectorAll('.tab-container').forEach(content => {
|
|
content.classList.remove('active');
|
|
});
|
|
document.getElementById('tab-' + tab + '-content').classList.add('active');
|
|
|
|
// Toggle required attributes
|
|
const existingFields = ['id_user'];
|
|
const newFields = ['new_email', 'new_first_name', 'new_last_name', 'new_password', 'new_password_confirm'];
|
|
|
|
if (tab === 'existing') {
|
|
existingFields.forEach(id => {
|
|
document.getElementById(id).setAttribute('required', 'required');
|
|
});
|
|
newFields.forEach(id => {
|
|
document.getElementById(id).removeAttribute('required');
|
|
});
|
|
} else {
|
|
existingFields.forEach(id => {
|
|
document.getElementById(id).removeAttribute('required');
|
|
});
|
|
newFields.forEach(id => {
|
|
document.getElementById(id).setAttribute('required', 'required');
|
|
});
|
|
}
|
|
}
|
|
|
|
function showAlert(type, message) {
|
|
const alertClass = type === 'success' ? 'bg-emerald-500' : 'bg-red-500';
|
|
const icon = type === 'success' ? 'check-circle' : 'alert-circle';
|
|
|
|
const alert = document.createElement('div');
|
|
alert.className = `${alertClass} text-white px-6 py-4 rounded-xl shadow-xl z-50 flex items-center gap-3 fixed top-4 right-4`;
|
|
alert.innerHTML = `
|
|
<i data-lucide="${icon}" class="w-5 h-5"></i>
|
|
<span class="font-semibold">${message}</span>
|
|
<button type="button" onclick="this.parentElement.remove()" class="text-white/80 hover:text-white">
|
|
<i data-lucide="x" class="w-4 h-4"></i>
|
|
</button>
|
|
`;
|
|
|
|
document.body.appendChild(alert);
|
|
lucide.createIcons();
|
|
|
|
setTimeout(() => {
|
|
alert.remove();
|
|
}, 5000);
|
|
}
|
|
</script>
|
|
{% endblock %}
|