262 lines
11 KiB
HTML
262 lines
11 KiB
HTML
|
||
{% extends 'base.html' %}
|
||
{% load static django_bootstrap5 %}
|
||
|
||
{% block title %}Регистрация сотрудника{% endblock %}
|
||
|
||
{# ВАЖНО: применяем тему ещё в <head>, чтобы не было «мигания» темы #}
|
||
{% block extra_css %}
|
||
<script src="{% static 'js/utils/themeInitializer.js' %}"></script>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="row justify-content-center">
|
||
<div class="col-lg-8">
|
||
<div class="card shadow">
|
||
{# Заголовок без жёсткой покраски — следуем теме #}
|
||
<div class="card-header bg-primary text-white">
|
||
<h3 class="mb-0">Регистрация нового сотрудника</h3>
|
||
</div>
|
||
|
||
<div class="card-body">
|
||
<form method="post" enctype="multipart/form-data" novalidate>
|
||
{% csrf_token %}
|
||
|
||
<!-- Контакты -->
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-md-6">
|
||
{% bootstrap_field form.email show_label=False placeholder="E-mail" %}
|
||
</div>
|
||
<div class="col-md-6">
|
||
{% bootstrap_field form.phone_number show_label=False placeholder="Телефон" %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Мессенджеры -->
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-md-4 position-relative">
|
||
{% bootstrap_field form.whatsapp show_label=False placeholder="WhatsApp" %}
|
||
<span class="position-absolute top-50 end-0 translate-middle-y me-3">
|
||
<i class="bi bi-whatsapp text-success"></i>
|
||
</span>
|
||
</div>
|
||
<div class="col-md-4 position-relative">
|
||
{% bootstrap_field form.telegram show_label=False placeholder="Telegram" %}
|
||
<span class="position-absolute top-50 end-0 translate-middle-y me-3">
|
||
<i class="bi bi-telegram"></i>
|
||
</span>
|
||
</div>
|
||
<div class="col-md-4 position-relative">
|
||
{% bootstrap_field form.wechat show_label=False placeholder="WeChat" %}
|
||
<span class="position-absolute top-50 end-0 translate-middle-y me-3">
|
||
<i class="bi bi-wechat" style="color:#07C160;"></i>
|
||
</span>
|
||
</div>
|
||
|
||
{% if form.non_field_errors %}
|
||
<div class="alert alert-danger alert-dismissible fade show mb-0 mt-3" style="border-radius:8px;">
|
||
{% for error in form.non_field_errors %}
|
||
<i class="bi bi-exclamation-circle me-2"></i>{{ error }}
|
||
{% endfor %}
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- ФИО -->
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-md-4">{% bootstrap_field form.last_name show_label=False placeholder="Фамилия" %}</div>
|
||
<div class="col-md-4">{% bootstrap_field form.first_name show_label=False placeholder="Имя" %}</div>
|
||
<div class="col-md-4">{% bootstrap_field form.patronymic show_label=False placeholder="Отчество" %}</div>
|
||
</div>
|
||
|
||
<!-- Дата рождения + Пол -->
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-md-4">
|
||
{% bootstrap_field form.birth_date show_label=False placeholder="Дата рождения" %}
|
||
</div>
|
||
<div class="col-md-8">
|
||
<label class="form-label d-block mb-2">Пол <span class="text-danger">*</span></label>
|
||
{% bootstrap_field form.gender show_label=False %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Пароли -->
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-md-6 position-relative">
|
||
{% bootstrap_field form.password1 show_label=False placeholder="Пароль" %}
|
||
<span class="password-toggle position-absolute top-50 end-0 translate-middle-y me-3 pe-5"
|
||
data-target="#{{ form.password1.auto_id }}"
|
||
style="cursor:pointer; z-index:10;">
|
||
<i class="bi bi-eye-slash"></i>
|
||
</span>
|
||
</div>
|
||
<div class="col-md-6 position-relative">
|
||
{% bootstrap_field form.password2 show_label=False placeholder="Повторите пароль" %}
|
||
<span class="password-toggle position-absolute top-50 end-0 translate-middle-y me-3 pe-5"
|
||
data-target="#{{ form.password2.auto_id }}"
|
||
style="cursor:pointer; z-index:10;">
|
||
<i class="bi bi-eye-slash"></i>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Аватар с превью -->
|
||
<div class="row g-3 align-items-center mb-4">
|
||
<div class="col-auto">
|
||
<div class="rounded-circle border bg-body-secondary d-flex align-items-center justify-content-center"
|
||
style="width:96px;height:96px;overflow:hidden;">
|
||
<img id="avatarPreview" src=""
|
||
alt="Превью аватара"
|
||
class="d-none"
|
||
style="width:100%;height:100%;object-fit:cover;">
|
||
<i id="avatarPlaceholder" class="bi bi-person-circle fs-1 text-secondary"></i>
|
||
</div>
|
||
</div>
|
||
<div class="col">
|
||
{% bootstrap_field form.avatar %}
|
||
<small class="text-muted">Рекомендуемый размер: 200×200 px</small>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-grid gap-2">
|
||
<button type="submit" class="btn btn-primary btn-lg">
|
||
<i class="bi bi-person-plus me-2"></i>Зарегистрироваться
|
||
</button>
|
||
<a href="{% url 'auth_front:login' %}" class="btn btn-link">Уже есть аккаунт? Войти</a>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script type="module">
|
||
import { initPasswordToggle } from "{% static 'js/components/passwordToggleHandler.js' %}";
|
||
import { initAvatarPreview } from "{% static 'js/components/avatarPreviewHandler.js' %}";
|
||
import { initLiveValidation } from "{% static 'js/components/liveValidationHandler.js' %}";
|
||
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
// Переключение видимости пароля
|
||
initPasswordToggle();
|
||
|
||
// Превью аватара
|
||
initAvatarPreview({
|
||
fileInputSelector: 'input[type="file"][name="{{ form.avatar.html_name }}"]',
|
||
previewImageId: 'avatarPreview',
|
||
placeholderId: 'avatarPlaceholder'
|
||
});
|
||
|
||
// Live validation - валидация в реальном времени
|
||
const validator = initLiveValidation({
|
||
formSelector: 'form',
|
||
debounce: 500, // задержка 500мс после ввода
|
||
rules: {
|
||
email: [
|
||
{ type: 'required', message: 'Email обязателен для заполнения' },
|
||
{ type: 'email', message: 'Введите корректный email (example@company.com)' }
|
||
],
|
||
phone_number: [
|
||
{ type: 'required', message: 'Номер телефона обязателен' },
|
||
{
|
||
type: 'pattern',
|
||
pattern: /^\+\d{11,14}$/,
|
||
message: 'Введите номер в формате +79991234567 (11-14 цифр с плюсом)'
|
||
}
|
||
],
|
||
first_name: [
|
||
{ type: 'required', message: 'Имя обязательно для заполнения' },
|
||
{ type: 'minLength', value: 2, message: 'Минимум 2 символа' },
|
||
{
|
||
type: 'pattern',
|
||
pattern: /^[^\d]+$/,
|
||
message: 'Имя не должно содержать цифры'
|
||
}
|
||
],
|
||
last_name: [
|
||
{ type: 'required', message: 'Фамилия обязательна для заполнения' },
|
||
{ type: 'minLength', value: 2, message: 'Минимум 2 символа' },
|
||
{
|
||
type: 'pattern',
|
||
pattern: /^[^\d]+$/,
|
||
message: 'Фамилия не должна содержать цифры'
|
||
}
|
||
],
|
||
patronymic: [
|
||
{
|
||
type: 'pattern',
|
||
pattern: /^[^\d]*$/,
|
||
message: 'Отчество не должно содержать цифры',
|
||
required: false // Поле опциональное
|
||
}
|
||
],
|
||
birth_date: [
|
||
{ type: 'required', message: 'Дата рождения обязательна' },
|
||
{
|
||
type: 'custom',
|
||
validate: (value) => {
|
||
const date = new Date(value);
|
||
const today = new Date();
|
||
const age = today.getFullYear() - date.getFullYear();
|
||
return age >= 18 && age <= 100;
|
||
},
|
||
message: 'Возраст должен быть от 18 до 100 лет'
|
||
}
|
||
],
|
||
gender: [
|
||
{ type: 'required', message: 'Необходимо указать пол' }
|
||
],
|
||
avatar: [
|
||
{ type: 'required', message: 'Необходимо загрузить фотографию' }
|
||
],
|
||
password1: [
|
||
{ type: 'required', message: 'Пароль обязателен' },
|
||
{ type: 'minLength', value: 6, message: 'Пароль должен содержать минимум 6 символов' }
|
||
],
|
||
password2: [
|
||
{ type: 'required', message: 'Подтвердите пароль' },
|
||
{
|
||
type: 'match',
|
||
field: 'password1',
|
||
message: 'Пароли не совпадают'
|
||
}
|
||
],
|
||
// Специальная проверка контактов (хотя бы один)
|
||
telegram: [
|
||
{
|
||
type: 'anyContact',
|
||
fields: ['telegram', 'whatsapp', 'wechat'],
|
||
message: 'Заполните хотя бы один мессенджер (Telegram, WhatsApp или WeChat)'
|
||
}
|
||
],
|
||
whatsapp: [
|
||
{
|
||
type: 'anyContact',
|
||
fields: ['telegram', 'whatsapp', 'wechat'],
|
||
message: 'Заполните хотя бы один мессенджер (Telegram, WhatsApp или WeChat)'
|
||
}
|
||
],
|
||
wechat: [
|
||
{
|
||
type: 'anyContact',
|
||
fields: ['telegram', 'whatsapp', 'wechat'],
|
||
message: 'Заполните хотя бы один мессенджер (Telegram, WhatsApp или WeChat)'
|
||
}
|
||
]
|
||
}
|
||
});
|
||
|
||
// При изменении password1 - ревалидируем password2
|
||
document.querySelector('[name="password1"]')?.addEventListener('input', () => {
|
||
const password2 = document.querySelector('[name="password2"]');
|
||
if (password2 && password2.value) {
|
||
validator.validateField('password2');
|
||
}
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %}
|
||
|