- Полная переделка шаблона request_detail.html с единообразными стилями
- Новая структура: основная карточка (8 колонок) + боковая панель комментариев (4 колонки)
- Единообразные иконки типов заявлений (calendar-check, capsule, umbrella-fill и т.д.)
- Единообразные бейджи статуса и типа как в списке
- Информационные блоки для автора, описания, получателей, одобрившего
- Форма комментариев с правильным стилем message-field--compact
- Emoji picker с поддержкой dropdown-menu поверх контента
- AJAX обработка отправки и удаления комментариев
- Новый CSS файл request-detail.css с адаптивностью
- Добавлена функция _add_type_display() в views.py для отображения типа
Улучшения:
- Единство дизайна со страницей списка заявлений
- Правильное позиционирование emoji picker (fixed positioning)
- Sticky боковая панель с комментариями
- Форма комментария как в ленте (message-field--compact)
- Кнопки одобрения/отклонения с правильными иконками
Исправлено:
- Добавлена правильная структура card-header с аватаром 48x48px
- Добавлены все необходимые детали: получатели, копия, согласующий
- Исправлены стили для badge и card-body
- Добавлена корректная форма комментария с emoji picker и auto-expand
- Подключены правильные JavaScript компоненты
- Исправлен формат дат (d.m.Y H:i)
- Добавлена передача can_process в контексте view
Добавлено:
- Иконки для каждого раздела (получатели, копия, согласующий)
- Ссылки на карточки сотрудников
- Правильные Badge с subtle цветами
- Форма комментария с message-field компонентом
- Approver (тот кто принял решение) не получает уведомление о своем действии
- Исключены employee.id и approver_id из recipients при approve/reject
- Упрощен signal create_request_notifications (убран лишний запрос в БД)
- Теперь передается актуальный request_obj с установленным approver_id
MOBILE FIXES:
- request-list.css: переделан мобильный layout с flexbox вместо grid
- page-header.css: оптимизирован padding/margin для мобильных экранов
- requestListHandler.js:
* Добавлен AbortController с таймаутом (10/15 сек для мобильных)
* Обработка offline/online событий сети
* Информативные сообщения об ошибках (отсутствие интернета, таймаут)
* Адаптивный Intersection Observer (rootMargin 200px для мобильных)
* Лучшая обработка network failures
UI/UX:
- Скрыт счётчик на экранах < 480px
- Контент теперь отображается на всех разрешениях
- Исчезновение спиннера при ошибке сети
- Визуальное различие между ошибками загрузки
NEW FEATURE:
- Добавлен новый тип заявления 'Отгул' (day_off)
- Обновлена миграция 0009_alter_request_type
- Добавлен во все формы создания/редактирования
- Добавлен в фильтры списка
- Форма добавления комментария теперь обрабатывается через AJAX
- Форма удаления комментария с подтверждением
- Форма изменения статуса с подтверждением
- После успешных операций страница перезагружается
- Отображение состояния загрузки (spinner) на кнопках
- Обработка ошибок с alert уведомлениями
- Использованы компоненты card-list и message-field
- Убраны inline стили, используются классы проекта
- Улучшена структура и читаемость
- Добавлен правильный layout с container-fluid
- Комментарии теперь используют card-list-item класс
- Форма комментариев использует message-field компонент
- Заменено c.author_name на c.author.full_name|default:c.author.display_name
- Заменено c.author_id на c.author.id (вложенный объект)
- API возвращает вложенный объект author, а не плоские поля
- Заменено request_obj.employee.username на employee.display_name
- API возвращает full_name и display_name, но не username
- Исправлена ошибка VariableDoesNotExist при рендеринге шаблона
- Добавлен обработчик для смены статуса заявления в JavaScript
- Интегрирована форма смены статуса в шаблон request_detail.html
- Форма видна только администраторам (can_process=True)
- Поддерживаются переходы: pending->approved/rejected, *->cancelled
- Добавлена валидация и подтверждение перед изменением статуса
- Используются API endpoints: /api/v1/requests/{id}/approve/, reject/, cancel/
- Автоматическое обновление содержимого модали после смены статуса
- Отображение статуса обновления (loading, success, error) в UI
- Добавлена функция удаления комментариев в views_front.py
- Добавлена обработка удаления в URL routing
- Расширен JavaScript модуль для работы с добавлением/удалением комментариев
- Добавлена обработка формы добавления комментариев с JSON
- Улучшена обработка ошибок при добавлении комментария
- Добавлена инициализация emoji picker в модали
- Обновлен счетчик комментариев при добавлении/удалении
- Обновлены шаблоны для работы с данными из API
- Добавлена поддержка перезагрузки содержимого модали после каждого действия
Этап 3 разработки включает:
✅ Создан JavaScript модуль requestDetail.js:
- Класс RequestDetailModal для управления модальным окном
- Функция open(requestId) для открытия заявления
- Функция loadData(requestId) для загрузки через AJAX
- Обработка ошибок (404, 403)
- Поддержка добавления комментариев в модали
- initRequestDetailModal() для инициализации на странице список
✅ Добавлено модальное окно в request_list_full.html:
- HTML структура модального окна requestDetailModal
- ID контейнера для содержимого: requestDetailContent
- Bootstrap 5 modal с размером modal-lg
- Scrollable модаль для больших содержимо
✅ Обновлена инициализация скриптов:
- Подключение модуля requestDetail.js
- Инициализация RequestDetailModal при загрузке страницы
- Сохранение экземпляра для использования на странице
✅ Интеграция с кликами:
- Подготовка к интеграции с существующей логикой списка
- Возможность открытия заявления из списка или по прямой ссылке
Готово для следующего этапа 4: Функциональность комментариев
Этап 2 разработки включает:
✅ Создан RequestDetailView класс в views_front.py:
- Наследуется от LoginRequiredMixin, TemplateView
- Загружает данные о заявлении через API (/api/v1/requests/{pk}/)
- Загружает комментарии к заявлению
- Проверяет права доступа (обрабатывает 404, 403)
- Форматирует даты для отображения
✅ Обновлены URLs (urls_front.py):
- Добавлен маршрут: path('<int:pk>/', RequestDetailView.as_view(), name='request_detail')
- Импортирован RequestDetailView
✅ Обновлен шаблон request_detail.html:
- Раскомментированы комментарии (были закрыты)
- Добавлена обработка ошибок (404, 403)
- Оформлено отображение заявления и комментариев
- Включен функционал добавления комментариев
� URLs в notification_signals.py уже правильные (проверено)
Готово для следующего этапа 3: Frontend разработка
- Добавлен отдельный контейнер для данных заявлений
- JS работает только с #reqListContent, шапка всегда видна
- Исправлен порядок заявлений (order_by)
- comments_count через annotate
- Исправлен обработчик формы комментария (preventDefault)
- Удаление начального спиннера после загрузки
- Обновлен requestListHandler.js: рендеринг в табличном формате
- Сохранена структура таблицы req-table-header + req-row
- Шаблон: удален SSR-цикл, сохранена шапка таблицы
- Добавлены данные пользователя в dataset (userId, canProcess)
- Включен useAjaxList режим в initRequestListPage
- Удалены старые CSS для req-item (не используются)
- Структура совместима с существующими стилями request-list.css
- Шаблоны используют obj.attachment_url напрямую из API
- Удален templatetag absolute_url (больше не нужен)
- Упрощена проверка наличия вложения
- Создан requestListHandler.js для AJAX загрузки с infinite scroll
- Обновлен requestList.js для поддержки AJAX режима
- Подход аналогичен приложению документов
- Добавлен templatetag absolute_url для преобразования относительных путей
- Обновлены все шаблоны заявлений: request_list_full, request_detail, request_process
- Теперь все ссылки на вложения используют {% absolute_url %} для полных URL
- Добавлен use_url=True для attachment_url в сериализаторе API
- Исправлена проблема с локальными URL вместо доменных
- Добавлен attachment_url для полного URL к прикрепленному файлу
- Исправлен шаблон my_requests.html (удалены лишние теги)
- Теперь API заявлений работает аналогично API документов
- Создан компонент requestListHandler.js для AJAX загрузки
- Упрощен view my_requests - данные загружаются через API
- Обновлен шаблон my_requests.html на AJAX загрузку
- Добавлен поиск по типу заявления и комментарию
- Intersection Observer для автоматической подгрузки
- Фильтрация по статусу заявления
- Удалена серверная пагинация
- Добавлены спиннеры загрузки
Стили комментариев:
- Приведены к единому стилю с лентой новостей
- Убрана фоновая подложка блока комментариев
- Убрана разделительная линия между заявлением и комментариями
- Обновлена структура: comment-header, comment-body, comment-separator
- Добавлены кликабельные ссылки на профили авторов
Исправления формы комментариев:
- Исправлена перезагрузка страницы при нажатии Enter
- Улучшена обработка submit события (preventDefault + stopPropagation)
- Правильная проверка формы через matches() вместо closest()
API endpoints:
- Использование правильных API endpoints для комментариев
- Переход с /requests/comments/{id}/ на /api/v1/requests/{id}/comments/
- Устранено дублирование запросов к старым и новым endpoints
CSRF токены:
- Добавлена функция getCsrfToken() для извлечения из cookie
- CSRF токен добавлен во все POST запросы к API
- Исправлены ошибки 403 Forbidden для действий (cancel, approve, reject)
Расширенные фильтры:
- Добавлен фильтр 'Все доступные' (свои + адресованные + с правами)
- Добавлен фильтр 'Мои заявления' (только созданные мной)
- Добавлен фильтр 'Мне адресовано' (recipients + cc_users + sent_to_all_department)
- Поддержка параметра addressed_to_me=true в API
- Обновлена логика views_front.py для обработки новых фильтров
- Исправлены lint ошибки (длинные строки)
- Создан filter format_iso_date в req_tags.py
- Парсит ISO даты из API (с/без времени, с/без Z)
- Поддерживает форматы: d.m.Y, H:i, d.m.Y H:i, Y-m-d
- Обновлен шаблон для использования нового фильтра
- Более читаемое и надежное отображение дат и времени
Проблема: API возвращает даты как строки ISO, фильтр date не работал
Решение: используется slice для извлечения даты и времени из ISO строк
- date_from/date_to: slice:":10" для YYYY-MM-DD
- created_at: slice:":10" для даты, slice:"11:16" для времени
- Добавлены проверки на существование значений
Структура таблицы:
- Иконка (48px) - тип заявления
- Заголовок (расширяемая, 2fr) - название, тип, превью комментария
- Автор (1fr) - имя сотрудника с ссылкой
- Даты (100px) - date_from и date_to
- Статус (110px) - badge со статусом
- Создано (80px) - дата и время создания
- Действия (80px) - кнопки одобрить/отклонить/редактировать
Особенности:
- Grid layout с фиксированными шириной колонок
- Автоматический перенос текста в пределах ячейки (word-wrap)
- Выравнивание всех элементов по сетке
- Футер с комментариями выровнен с содержимым
- Адаптивный дизайн (планшеты, мобильные)
- На мобильных - две колонки (иконка + остальное)
- Сеточный layout с выравниванием колонок (иконка, метаданные, действия)
- Убраны разделительные линии между блоками внутри заявления
- Тонкая линия между заявлениями для визуального разделения
- Убрана граница над блоком комментариев (card-actions border-top: none)
- Выравнивание комментариев по левому краю текста заявления
- Адаптивный дизайн для мобильных устройств
- Мягкий hover-эффект для строк
Переработан RecipientPicker компонент:
✨ Chip-based UI (как в Gmail/Outlook)
- Пользователи отображаются как chips с крестиком для удаления
- Autocomplete с живым поиском по имени/email
- Dropdown с аватарами и должностями пользователей
� Интуитивный интерфейс:
- Структура To:/CC:/Отделы: (как в почтовых клиентах)
- Получатели всегда видны (убраны collapse)
- Кнопка '+ Копия' для показа поля CC
- Inline флаг 'Всем сотрудникам'
� Улучшенная верстка:
- Модальные окна расширены до modal-lg
- Получатели перенесены в начало (как тема в email)
- Добавлены иконки для визуальной группировки
- Добавлены emoji в типы заявлений для удобства
� CSS обновлен:
- Email-style компактный дизайн
- Фокус-состояния для inputs
- Hover-эффекты для dropdown items
- Адаптивность для мобильных устройств
- Поддержка темной темы
� UX улучшения:
- Поиск с автодополнением вместо select
- Визуальные аватары пользователей (инициалы)
- Показ должности и email в dropdown
- Автофокус после добавления пользователя
- Ограничение результатов поиска до 10
Создан компонент RecipientPicker:
- Множественный выбор отделов (ManyToMany)
- Выбор основных получателей (recipients)
- Выбор пользователей в копии (CC users)
- Флаг "Отправить всем сотрудникам отделов"
- Динамический рендеринг выбранных пользователей
- Валидация: нельзя указать recipients с флагом sent_to_all_department
Интеграция:
- Добавлены секции получателей в модальные окна создания и редактирования
- Обновлен requestCrudHandler.js для передачи данных получателей в API
- Добавлены стили recipientPicker.css
- Подключен CSS в request_list_full.html
Функциональность:
- Загрузка списка пользователей и отделов из API
- Добавление/удаление получателей через UI
- Автоматическая очистка форм после создания
- Поддержка редактирования существующих получателей
- Заменен лишний div.container на стандартный container для единообразия
- Заменен класс .card на .ios-card в секции активности для устранения конфликтов стилей
- Добавлен card-list.css в base.html для корректного отображения карточек новостей
- Добавлены CSS правила для правильного поведения .row и колонок в grid-layout
- Исправлена проблема с выходом календаря за границы страницы
- Убраны видимые скроллбары у modal-body и modal-content для чистого UI
- ApiClient теперь обрабатывает 401 и 403 ошибки для обновления токена
- Добавлен JWTRefreshMiddleware для проактивного обновления токенов за 5 минут до истечения
- Добавлена настройка JWT_REFRESH_THRESHOLD_MINUTES (по умолчанию 5)
- Исправлена проблема с периодическими 403 ошибками после истечения access token
- Fix department_list.html: use variable for create_url instead of inline yesno with url tag
- Fix feed_list.html: properly handle conditional create_url with with blocks
- Fix document_list.html: use variable for create_modal instead of inline yesno
- Fix request_list_full.html: simplify scope toggle by placing it separately after header
Django templates don't allow nested template tags in include parameters,
so we now prepare variables beforehand using if/with blocks.
- Fix bug in page_header.html: add # to data-bs-target for card create button
- Integrate page_header in department_list.html with search and create card
- Integrate page_header in feed_list.html (no search, staff-only create button)
- Integrate page_header in document_list.html with Mine/All scope toggle
- Integrate page_header in request_list_full.html with filters and scope toggle
- Integrate page_header in chat_list.html with search
- Create _filters.html template for requests_app
- Add page-header.css to all integrated templates for consistent styling
Date: Mon, 22 Sep 2025 10:30:00 +0300
Subject: [requests] Drawer «Рассмотреть», корректный чекбокс «Черновик», дружелюбные даты,
robust-обработка ошибок reject, и глобальный бейдж непрочитанных чатов
- Список заявлений:
- вместо россыпи кнопок добавлен аккуратный выезжающий блок действий (collapse drawer)
по клику «Рассмотреть»; компактные нейтральные стили, мягкая анимация.
- в модале «Изменить» добавлена галочка «Сохранить как черновик» с правильной
семантикой: hidden `_save_as=submit` по умолчанию + checkbox `_save_as=draft`.
Теперь снятие галочки действительно снимает draft.
- улучшены стили и структура разметки.
- Даты:
- на фронт-обёртке нормализуем datetime-поля (created_at/updated_at/decided_at)
в человекочитаемый вид, чтобы не светить сырые ISO 8601.
- Обработка ошибок:
- фикс падения при попытке отклонить уже одобренную заявку: ответ 400 у DRF
может приходить списком ошибок; теперь сообщение извлекается безопасно.
- Чаты:
- синхронизировали название ключа контекста с проектом (`chat_unread_total`);
- бейдж «Чаты» обновляется глобальным событием `chats:unread-total`, даже если
список чатов не открыт (fallback — суммирование счётчиков на странице).
Примечание: пути к шаблонам могут отличаться в вашем проекте — ниже даны
типовые варианты (`backend/requests_app/templates/...`). Если у вас другой
layout, внесите изменения в соответствующие файлы шаблонов.
Большое обновление календаря. Объединили модель событий, добавили полноценную повторяемость (hourly/daily/weekly/monthly/annual), материализацию вхождений в списке, навели порядок с правами отделов и обновили фронтовой шаблон. Покрыли API pytest-тестами и починили мелкие баги.
### Ключевые изменения
**Модель**
* Заменили `CompanyEvent` и `DepartmentEvent` на единую **`CalendarEvent`**:
* `department` (nullable FK) — если задан, событие отдела; если `NULL`, событие компании.
* Даты/время: `start_date`, `end_date`, `start_time`, `end_time`, `all_day`.
* Повторяемость: `recurrence` ∈ {`one_time`, `hourly`, `daily`, `weekly`, `monthly`, `annual`}.
* Параметры повторов: `recurrence_interval` (int>0), `recurrence_until` (date), `recurrence_count` (int), `weekdays_mask` (bitmask для weekly).
* Индексы под выборки и сортировку; check-constraint `cal_event_date_time_range_valid` (валидность диапазонов даты/времени; для одного дня `start_time <= end_time` при наличии обоих).
* Поля оформления: `title`, `description`, `color`, `location`, `created_by`, `created_at`.
* Правка по умолчаниям: `weekdays_mask` теперь с безопасным default=0 (исправляет `NOT NULL` в SQLite).
**Сервисы**
* Добавлен сервис `expand_event_occurrences(event, range_start, range_end, max_instances=1000)`:
* Материализует все вхождения события в окне `[range_start, range_end)`.
* Корректно обрабатывает: leap day (29 Feb), месяцы с «дырками» (31-е), еженедельные маски, ежечасные интервалы.
* Выдаёт ровные границы дат/датавремени для FullCalendar.
**Сериализаторы**
* `CalendarEventWriteSerializer` — строгая валидация:
* Для hourly требуется `start_time` и `end_time`.
* Для weekly — пустой/непустой `weekdays` валидируется и мапится в `weekdays_mask`.
* `recurrence_interval` > 0; взаимоисключение `recurrence_until` и `recurrence_count`.
* `CalendarEventSerializer` — read-вариант с плоскими полями для фронта.
* Доп. сериализатор вьюхи для списка не нужен — материализуем в контроллере (см. ниже).
**ViewSet / API**
* Новый единый роут: **`/api/v1/calendar/events/`** (`ModelViewSet`):
* `list`: возвращает **материализованные** вхождения в окне `[start, end)` (query-параметры `YYYY-MM-DD`). Если передан `department_id` — фильтрует события отдела, иначе — компании.
* `retrieve/create/update/partial_update/destroy`: операции над самим `CalendarEvent`.
* `get_queryset`: для `list` фильтрует по department/company, для detail — полный QS.
* При `create`: подставляет `department_id` либо из контекста/параметров, либо из body, проставляет `created_by`, чистит кеш.
* **Права**:
* Используется комбинированный пермишен **`ManageCalendarPerm(AdminOrDeptAllowed)`**.
* Для компании делегирует на `AdminOrActionOrModelPerms`.
* Для отдела требует **строковый код**: `"manage_department_events"`.
* Обновляет инстанс пользователя из БД (сбрасывает кэши), чтобы в одном тесте «выдали право → сразу работает».
* `get_permissions()` → `[IsAuthenticated(), ManageCalendarPerm()]`.
* Починки:
* Подключены `parse_date`/`status` где нужно.
* Исправлен конфликт дат/датавремени в материализации.
* Возврат полей в `list`: `start`, `end`, `allDay`, `color`, `recurrence`, `department_id` (ID родителя можно оставить как `event.id`).
**Signals**
* Добавлен сигнал на изменение `date_of_birth` сотрудника:
* Создаёт/обновляет/удаляет соответствующее ежегодное событие в **глобальном** календаре.
* Идентификация через `source="employee_birthday"` + `source_ref=<employee_id>`.
**Админка**
* `CalendarEventAdmin`:
* Поиск по названию/описанию/локации.
* Фильтры по отделу, типу повторения, all\_day, наличию времени, «company vs department».
* Инлайнов нет; list-представление упрощено (исправлены ошибки `admin.E116` — фильтры теперь валидные `SimpleListFilter`/поля).
**Фронт (шаблон правой колонки)**
* Переписан `<script>` под единый эндпоинт:
* Список: GET `/api/v1/calendar/events/?[department_id=...]&start=YYYY-MM-DD&end=YYYY-MM-DD`.
* Создание:
* Компания: POST `/api/v1/calendar/events/` с `{title, start_date, [end_date], all_day}`.
* Отдел: POST туда же, но с `department_id` (в query или в body).
* Если указано время — **оба** (`start_time`, `end_time`) обязательны; иначе сохраняем как all-day.
* Нормализация дат в локали, аккуратные конвертации для FullCalendar.
* Заголовки авторизации: `Authorization: Bearer <token>` из `<meta name="api-access">`/`localStorage.api.access`. Добавлен fallback на CSRF-куку при отсутствии токена (для SessionAuth).
* Улучшен UI выбора отдела (`/api/v1/departments/my-departments/`), подписи, недельная лента.
**Тесты**
* Добавлены/актуализированы pytest-тесты API:
* Роуты и методы (CRUD).
* Позитив/негатив создания (валидация дат/времени/повторов).
* Изменение повторяемости (weekly → daily и пр.).
* Материализация (hourly alignment, weekly mask, monthly edge-cases, leap day).
* Права: отдел без/с `manage_department_events` (фикс за счёт строкового кода).
* Сигналы: апсерт/удаление дней рождения.
* Исправлены прежние падения: `NOT NULL weekdays_mask`, `NameError(parse_date/status)`, сравнения date/datetime, несоответствие `all_day` vs `allDay`, и др.
### Совместимость и заметки по деплою
* **BREAKING**: единый эндпоинт `/api/v1/calendar/events/` заменяет старые `/company-events/` и `/department-events/`. Если есть сторонние клиенты — переориентировать на новый.
* Миграции обязательны (новая таблица, индексы, дефолты).
* Если где-то ещё использовались константы прав — убедиться, что везде строка `"manage_department_events"` (тесты ориентируются на неё).
* Для фронта: убедиться, что реально прокидывается access-токен в `<meta name="api-access">` **или** есть валидная CSRF-кука при SessionAuth. Иначе POST вернёт 401.
### Почему так
* Единая модель/роут упрощают код и устраняют разъезд схем компании/отделов.
* Материализация в контроллере избавляет фронт от логики повторов и держит поведение единообразным.
* Явная строка кода права синхронизирована с тестовой выдачей пермишена и реальным хранилищем прав отделов.
* Валидация сериализатора закрывает большую часть негативных кейсов на уровне API.