Календарь (rightbar) : • Цвет события по умолчанию (#0d6efd) + удобная палитра (input[type=color] + быстрые свотчи). • Детали события в модале (центрирование, перенос модала в <body> при необходимости), кнопки Редактировать/Удалить. • Редактирование: заполнение формы из деталки, PATCH по API, возврат к календарю, безопасные обработчики (навешиваются один раз). • Еженедельная лента теперь собирает события со всех доступных календарей (компания + отделы пользователя), дедупликация по id. • В ленте недели: учитывается цвет события, показ источника (Компания / Отдел: N), location и краткое описание (≤20 символов). • Устранены дубли отделов в селекторе календарей; нормализованы id; аккуратные ошибки/403; безопасная работа без токена. Список отделов: • Поиск по названию и руководителю (data-haystack); счётчик сотрудников обновляется только по видимым строкам. • Кнопка «Создать отдел» в стиле списка (вся строка — кликабельна), видна только при наличии прав/статуса. • Чёткое центрирование текста и иконки без кастомных стилей (Bootstrap-утилиты), стрелка фиксирована справа. • Прокрутка только у списка (#deptList): шапка, поиск и кнопка остаются на месте; высота как в employees_list.html. Auth / Регистрация / Логин / Верификация email / Сброс пароля: • Шаблоны приведены к единой теме: уважаем переключатель тем из base.html, устранён «мигание» темы. • RegistrationForm: валидация контактов (хотя бы один из Telegram/WhatsApp/WeChat), пароли совпадают. • Поля телефона (регистрация/логин): inputmode='numeric', pattern для E.164, подсказки; на бэке по-прежнему нормализуется. • Регистрация: превью аватара с заглушкой, radio для пола; убраны поля навыков/должности из формы (они не нужны здесь). • API регистрации: принимаем avatar через Base64ImageField и сохраняем user.avatar при создании; остальная логика без изменений. • Сообщение об ошибке «нужно хотя бы одно контактное поле» возвращается в ключе detail (совместимо с тестом). • Логин/верификация/сброс: единые заголовки, floating labels, иконки, соответствие теме. Техническое: • Аккуратные утилиты JS: извлечение токена, формирование URL с диапазонами дат, нормализация событий, один раз навешиваемые обработчики. • Безопасные fallbacks для отсутствующих узлов/данных; кросс-браузерные мелочи (overflow-scrolling, aria-атрибуты). Примечания: • Миграций БД нет. Изменены шаблоны, фронтовый JS и вью/сериализатор регистрации. • Если пути файлов в проекте отличаются, коммит всё равно включает все изменённые файлы (git add -A).
83 lines
2.8 KiB
HTML
83 lines
2.8 KiB
HTML
{% extends 'base.html' %}
|
||
{% load static %}
|
||
|
||
{% block title %}Сброс пароля{% endblock %}
|
||
|
||
{# Раннее применение темы, синхронно с переключателем в base.html #}
|
||
{% block extra_css %}
|
||
<script>
|
||
(function(){
|
||
const root = document.documentElement;
|
||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||
const getSaved = () => localStorage.getItem('theme') || 'auto';
|
||
const effective = (m) => m === 'auto' ? (media.matches ? 'dark' : 'light') : m;
|
||
const apply = () => root.setAttribute('data-bs-theme', effective(getSaved()));
|
||
apply();
|
||
media.addEventListener('change', () => { if (getSaved()==='auto') apply(); });
|
||
})();
|
||
</script>
|
||
{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="row justify-content-center">
|
||
<div class="col-lg-6">
|
||
<div class="card shadow">
|
||
<div class="card-header bg-primary text-white">
|
||
<h3 class="mb-0">Сброс пароля</h3>
|
||
</div>
|
||
|
||
<div class="card-body">
|
||
{# Общие ошибки формы #}
|
||
{% if form.non_field_errors %}
|
||
<div class="alert alert-danger alert-dismissible fade show mb-4" role="alert">
|
||
{% for error in form.non_field_errors %}
|
||
<i class="bi bi-exclamation-triangle me-2"></i>{{ error }}
|
||
{% if not forloop.last %}<br>{% endif %}
|
||
{% endfor %}
|
||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<form method="post" novalidate>
|
||
{% csrf_token %}
|
||
|
||
<div class="mb-4 form-floating">
|
||
<input
|
||
type="email"
|
||
name="{{ form.email.html_name }}"
|
||
id="{{ form.email.auto_id|default:'id_email' }}"
|
||
class="form-control {% if form.email.errors %}is-invalid{% endif %}"
|
||
placeholder="Email"
|
||
value="{{ form.email.value|default:'' }}"
|
||
inputmode="email"
|
||
autocomplete="email"
|
||
required
|
||
>
|
||
<label for="{{ form.email.auto_id|default:'id_email' }}">Email</label>
|
||
{% if form.email.errors %}
|
||
<div class="invalid-feedback">
|
||
{{ form.email.errors|striptags }}
|
||
</div>
|
||
{% else %}
|
||
<div class="form-text">Мы отправим инструкции по сбросу на этот адрес.</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary btn-lg w-100">
|
||
<i class="bi bi-envelope-arrow-up me-2"></i>Отправить инструкции
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', function(){
|
||
document.getElementById('{{ form.email.auto_id|default:"id_email" }}')?.focus();
|
||
});
|
||
</script>
|
||
{% endblock %}
|