chore: Remove temporary documentation files from backend directory
This commit is contained in:
parent
d7c025f120
commit
edc4d52a26
@ -1,209 +0,0 @@
|
||||
# Исправления списка чатов
|
||||
|
||||
## Дата: 20 ноября 2025
|
||||
|
||||
---
|
||||
|
||||
## Проблемы
|
||||
|
||||
1. ❌ Код JavaScript отображался на странице
|
||||
2. ❌ Дублирование обработчиков событий фильтрации
|
||||
3. ❌ Ненужная секция "Закрепленные" (отдельная категория)
|
||||
4. ❌ Фильтр не работал
|
||||
|
||||
---
|
||||
|
||||
## Решение
|
||||
|
||||
### 1. Убрана секция "Закрепленные"
|
||||
|
||||
**Было:**
|
||||
```django
|
||||
<div class="feed-card chat-section mb-3" data-sec="pinned" id="pinnedSection" style="display:none;">
|
||||
<div class="feed-hd">
|
||||
<div class="feed-ava"><i class="bi-pin-angle-fill text-warning"></i></div>
|
||||
<div class="feed-meta flex-grow-1">
|
||||
<h6 class="mb-0 text-secondary">Закрепленные</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sec-pinned" class="list-chats"></div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
- Секция удалена полностью
|
||||
- Закрепленные чаты остаются внутри своих категорий (глобальный, отделы, личные, группы и т.д.)
|
||||
|
||||
### 2. Исправлено дублирование JavaScript
|
||||
|
||||
**Было:**
|
||||
```javascript
|
||||
// В одном месте
|
||||
applyBtn?.addEventListener('click', applyTypeFilter);
|
||||
resetBtn?.addEventListener('click', resetFilter);
|
||||
</script>
|
||||
|
||||
// И второй раз ниже
|
||||
if (chatTypeSelect) {
|
||||
chatTypeSelect.addEventListener('change', applyTypeFilter);
|
||||
}
|
||||
if (applyBtn) {
|
||||
applyBtn.addEventListener('click', applyTypeFilter); // ДУБЛЬ!
|
||||
}
|
||||
if (resetBtn) {
|
||||
resetBtn.addEventListener('click', resetFilter); // ДУБЛЬ!
|
||||
}
|
||||
applyTypeFilter();
|
||||
</script>
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```javascript
|
||||
applyBtn?.addEventListener('click', applyTypeFilter);
|
||||
resetBtn?.addEventListener('click', resetFilter);
|
||||
|
||||
// Применяем фильтр при загрузке
|
||||
applyTypeFilter();
|
||||
</script>
|
||||
|
||||
{# Подключаем chat-list-enhanced.js #}
|
||||
<script src="{% static 'js/chat-list-enhanced.js' %}"></script>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
### 3. Убрана опция "Закрепленные" из фильтра
|
||||
|
||||
**Было в `_filters.html`:**
|
||||
```html
|
||||
<option value="pinned">Закрепленные</option>
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
- Опция удалена
|
||||
- Фильтр работает с типами: все, глобальный, отделы, личные, группы, каналы, объявления
|
||||
|
||||
---
|
||||
|
||||
## Как работает закрепление
|
||||
|
||||
### Визуальное выделение
|
||||
Закрепленные чаты остаются в своих категориях, но **выделяются**:
|
||||
- 🎨 Желтый фон (`background: #fffbeb`)
|
||||
- 📌 Желтая левая граница (`border-left: 4px solid var(--bs-warning)`)
|
||||
- 🏷️ Бейдж с иконкой булавки в правом верхнем углу
|
||||
|
||||
### Сортировка
|
||||
`chat-list-enhanced.js` при загрузке и при закреплении:
|
||||
```javascript
|
||||
function moveToPinnedSection(chatRow) {
|
||||
const parent = chatRow.parentElement;
|
||||
const firstUnpinned = Array.from(parent.children).find(row =>
|
||||
!row.classList.contains('pinned')
|
||||
);
|
||||
|
||||
if (firstUnpinned) {
|
||||
parent.insertBefore(chatRow, firstUnpinned); // Вставляем перед первым незакрепленным
|
||||
} else {
|
||||
parent.appendChild(chatRow);
|
||||
}
|
||||
}
|
||||
```
|
||||
- Закрепленные чаты **перемещаются в начало** своей категории
|
||||
- Незакрепленные остаются ниже
|
||||
|
||||
### Пример структуры
|
||||
```
|
||||
📁 Личные
|
||||
📌 Иван Иванов (закреплен) ← желтый фон, иконка булавки
|
||||
📌 Мария Петрова (закреплена)
|
||||
👤 Алексей Сидоров
|
||||
👤 Ольга Николаева
|
||||
|
||||
📁 Группы
|
||||
📌 Отдел разработки (закреплена)
|
||||
👥 Проект X
|
||||
👥 HR обсуждения
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Контекстное меню
|
||||
|
||||
Правый клик на чате → меню:
|
||||
- 📌 **Закрепить** / Открепить
|
||||
- 🔔 Уведомления вкл/выкл
|
||||
- 👁️ Скрыть
|
||||
- ✅ Отметить прочитанным
|
||||
|
||||
**API запрос:**
|
||||
```javascript
|
||||
fetch(`/communications/api/chat/${chatId}/pin/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: `pinned=${!isPinned}`
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Проверка работы
|
||||
|
||||
### 1. Открыть страницу чатов
|
||||
```
|
||||
http://localhost:9000/communications/chats/
|
||||
```
|
||||
|
||||
### 2. Проверить фильтр
|
||||
- Выбрать "Личные" → должны показаться только личные чаты
|
||||
- Выбрать "Группы" → только группы
|
||||
- Выбрать "Все" → все категории видны
|
||||
|
||||
### 3. Проверить закрепление
|
||||
- Правый клик на любом чате
|
||||
- Выбрать "Закрепить"
|
||||
- Чат должен:
|
||||
- Получить желтый фон
|
||||
- Переместиться в начало своей категории
|
||||
- Показать бейдж с булавкой
|
||||
|
||||
### 4. Проверить открепление
|
||||
- Правый клик на закрепленном чате
|
||||
- Выбрать "Открепить"
|
||||
- Чат должен:
|
||||
- Вернуть обычный фон
|
||||
- Переместиться вниз категории
|
||||
- Убрать бейдж
|
||||
|
||||
---
|
||||
|
||||
## Файлы изменены
|
||||
|
||||
1. ✅ `backend/templates/communications/chat_list.html`
|
||||
- Убрана секция `#pinnedSection`
|
||||
- Исправлено дублирование JavaScript
|
||||
- Добавлен вызов `applyTypeFilter()` при загрузке
|
||||
|
||||
2. ✅ `backend/templates/communications/_filters.html`
|
||||
- Убрана опция `<option value="pinned">`
|
||||
|
||||
3. ⚙️ `backend/static/js/chat-list-enhanced.js` (без изменений)
|
||||
- Уже работает правильно с закреплением внутри категорий
|
||||
|
||||
4. ⚙️ `backend/static/css/components/chat-list-enhanced.css` (без изменений)
|
||||
- Уже есть стили `.chat-row.pinned` и `.chat-pinned-badge`
|
||||
|
||||
---
|
||||
|
||||
## Итог
|
||||
|
||||
✅ Код JavaScript больше не отображается на странице
|
||||
✅ Фильтр работает корректно
|
||||
✅ Закрепленные чаты остаются в своих категориях
|
||||
✅ Визуальное выделение закрепленных чатов (желтый фон + булавка)
|
||||
✅ Контекстное меню для управления закреплением
|
||||
✅ Нет дублирования обработчиков событий
|
||||
|
||||
Система готова к использованию! 🎉
|
||||
@ -1,392 +0,0 @@
|
||||
# Обновления списка чатов и модала создания
|
||||
|
||||
## Дата: 2024
|
||||
## Статус: Завершено
|
||||
|
||||
---
|
||||
|
||||
## Обзор изменений
|
||||
|
||||
Список чатов (`chat_list.html`) и модал создания чатов обновлены для поддержки нового функционала мессенджера со всеми 6 типами чатов.
|
||||
|
||||
---
|
||||
|
||||
## 1. Обновленный шаблон `chat_list.html`
|
||||
|
||||
### 1.1. Добавлены новые стили
|
||||
```django
|
||||
{% block extra_css %}
|
||||
...
|
||||
<link rel="stylesheet" href="{% static 'css/components/chat-list-enhanced.css' %}">
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
### 1.2. Добавлена секция "Закрепленные чаты"
|
||||
```django
|
||||
<div class="feed-card chat-section mb-3" data-sec="pinned" id="pinnedSection" style="display:none;">
|
||||
<div class="feed-hd">
|
||||
<div class="feed-ava" style="width:44px;height:44px;">
|
||||
<i class="bi-pin-angle-fill text-warning"></i>
|
||||
</div>
|
||||
<div class="feed-meta flex-grow-1">
|
||||
<h6 class="mb-0 text-secondary">Закрепленные</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div id="sec-pinned" class="list-chats"></div>
|
||||
</div>
|
||||
```
|
||||
- Изначально скрыта (`display:none`)
|
||||
- Будет заполняться JavaScript'ом через `chat-list-enhanced.js`
|
||||
- Закрепленные чаты отображаются первыми
|
||||
|
||||
### 1.3. Обновлены существующие секции
|
||||
**Глобальный чат:**
|
||||
- ✅ Добавлен условный вывод аватара: `{% if chat.avatar %}<img>{% else %}<i>{% endif %}`
|
||||
- ✅ Использование `chat.name|default:"Глобальный чат"`
|
||||
|
||||
**Отделы:**
|
||||
- ✅ Аватар чата (если задан)
|
||||
- ✅ Отображение `chat.name` для департаментов
|
||||
|
||||
**Личные чаты:**
|
||||
- ✅ Аватар собеседника
|
||||
- ✅ Корректная структура с `position-relative` для `stretched-link`
|
||||
|
||||
### 1.4. Добавлены новые секции
|
||||
|
||||
#### Группы (`data-sec="group"`)
|
||||
- Иконка: `bi-people-fill`
|
||||
- Отображает чаты с `chat.type == 'group'`
|
||||
- Поддержка аватара группы
|
||||
- Показывает название группы, количество сообщений, последнее сообщение
|
||||
|
||||
#### Каналы (`data-sec="channel"`)
|
||||
- Иконка: `bi-broadcast`
|
||||
- Отображает чаты с `chat.type == 'channel'`
|
||||
- Поддержка аватара канала
|
||||
- Показывает название, описание, метрики
|
||||
|
||||
#### Объявления (`data-sec="announcement"`)
|
||||
- Иконка: `bi-megaphone`
|
||||
- Отображает чаты с `chat.type == 'announcement'`
|
||||
- Поддержка аватара
|
||||
- Показывает название, описание, метрики
|
||||
|
||||
### 1.5. Все секции имеют:
|
||||
- `data-chat-id="{{ chat.pk }}"` - ID чата для WebSocket
|
||||
- `data-type="..."` - тип чата для фильтрации
|
||||
- `data-last-ts="..."` - timestamp последнего сообщения для сортировки
|
||||
- `data-haystack="..."` - текст для поиска
|
||||
- `data-unread` - бейдж непрочитанных сообщений (скрывается если 0)
|
||||
- Атрибуты для последнего сообщения: `data-last-time`, `data-last-author`, `data-last-preview`
|
||||
|
||||
---
|
||||
|
||||
## 2. Обновленный модал создания чатов
|
||||
|
||||
### 2.1. Селектор типа чата
|
||||
```html
|
||||
<select class="form-select" id="chatTypeSelect">
|
||||
<option value="private">Личный чат</option>
|
||||
<option value="group">Группа</option>
|
||||
<option value="channel">Канал</option>
|
||||
<option value="announcement">Объявления</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
### 2.2. Поля для группы/канала/объявлений
|
||||
```html
|
||||
<div id="groupFieldsSection" style="display:none;">
|
||||
<div class="mb-3">
|
||||
<label for="chatNameInput" class="form-label">Название чата <span class="text-danger">*</span></label>
|
||||
<input type="text" class="form-control" id="chatNameInput" placeholder="Введите название">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="chatDescInput" class="form-label">Описание</label>
|
||||
<textarea class="form-control" id="chatDescInput" rows="3" placeholder="Краткое описание чата"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
- Отображаются только для `group`, `channel`, `announcement`
|
||||
- Скрыты для `private`
|
||||
|
||||
### 2.3. Секция выбора участников
|
||||
```html
|
||||
<div id="recipientSection">
|
||||
<label class="form-label">Выберите участников</label>
|
||||
<div id="chatRecipients" class="recipient-picker" data-api-employees="...">
|
||||
...
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
- Для `private`: maxSelections = 1 (один собеседник)
|
||||
- Для `group`: maxSelections = 100 (множественный выбор)
|
||||
- Для `channel`/`announcement`: скрыта (участники опциональны)
|
||||
|
||||
### 2.4. Логика создания (JavaScript)
|
||||
|
||||
**Переключение типа чата:**
|
||||
```javascript
|
||||
chatTypeSelect.addEventListener('change', function() {
|
||||
const type = this.value;
|
||||
|
||||
if(type === 'private') {
|
||||
recipientSection.style.display = 'block';
|
||||
groupFieldsSection.style.display = 'none';
|
||||
chatPicker.setMaxSelections(1);
|
||||
} else if(type === 'group') {
|
||||
recipientSection.style.display = 'block';
|
||||
groupFieldsSection.style.display = 'block';
|
||||
chatPicker.setMaxSelections(100);
|
||||
} else {
|
||||
// channel, announcement
|
||||
recipientSection.style.display = 'none';
|
||||
groupFieldsSection.style.display = 'block';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**Создание чата:**
|
||||
```javascript
|
||||
if(type === 'private') {
|
||||
// Используем старый endpoint для личных чатов
|
||||
window.location.href = `/communications/start_private_chat/${employeeId}/`;
|
||||
} else {
|
||||
// Используем новый API для остальных типов
|
||||
const res = await fetch('/communications/api/chat/create/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: type,
|
||||
name: name,
|
||||
description: description,
|
||||
participant_ids: participant_ids // Только для группы
|
||||
})
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Обновленные фильтры (`_filters.html`)
|
||||
|
||||
```html
|
||||
<select name="chat_type" class="form-select" id="chatTypeFilter">
|
||||
<option value="all" selected>Все</option>
|
||||
<option value="pinned">Закрепленные</option>
|
||||
<option value="global">Глобальный</option>
|
||||
<option value="department">Отделы</option>
|
||||
<option value="private">Личные</option>
|
||||
<option value="group">Группы</option>
|
||||
<option value="channel">Каналы</option>
|
||||
<option value="announcement">Объявления</option>
|
||||
</select>
|
||||
```
|
||||
|
||||
**Логика фильтрации:**
|
||||
```javascript
|
||||
function applyTypeFilter() {
|
||||
const selectedType = chatTypeFilter?.value || 'all';
|
||||
const sections = document.querySelectorAll('.chat-section');
|
||||
|
||||
sections.forEach(section => {
|
||||
const sectionType = section.dataset.sec;
|
||||
if (selectedType === 'all' || selectedType === sectionType) {
|
||||
section.style.display = '';
|
||||
} else {
|
||||
section.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Обновления RecipientPicker
|
||||
|
||||
### 4.1. Добавлена поддержка `maxSelections`
|
||||
```javascript
|
||||
constructor(root, options) {
|
||||
...
|
||||
this.maxSelections = options.maxSelections || null; // null = без ограничений
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2. Проверка лимита при выборе
|
||||
```javascript
|
||||
if (checkbox.checked) {
|
||||
if (this.maxSelections && this.selected.size >= this.maxSelections) {
|
||||
checkbox.checked = false;
|
||||
alert(`Можно выбрать не более ${this.maxSelections} получателей`);
|
||||
return;
|
||||
}
|
||||
this.selected.set(id, payload);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3. Новые методы
|
||||
|
||||
**`getSelected()`** - получить выбранных с полными данными:
|
||||
```javascript
|
||||
getSelected() {
|
||||
return Array.from(this.selected.values());
|
||||
}
|
||||
```
|
||||
|
||||
**`setMaxSelections(max)`** - изменить лимит динамически:
|
||||
```javascript
|
||||
setMaxSelections(max) {
|
||||
this.maxSelections = max;
|
||||
// Если превышен лимит, сбрасываем лишние выборы
|
||||
if (max && this.selected.size > max) {
|
||||
const toKeep = Array.from(this.selected.entries()).slice(0, max);
|
||||
this.selected.clear();
|
||||
toKeep.forEach(([id, data]) => this.selected.set(id, data));
|
||||
this._renderSelected();
|
||||
this.elResults.querySelectorAll('input[type=checkbox][data-id]').forEach(checkbox => {
|
||||
const id = Number(checkbox.dataset.id);
|
||||
checkbox.checked = this.selected.has(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Подключение chat-list-enhanced.js
|
||||
|
||||
```html
|
||||
<script src="{% static 'js/chat-list-enhanced.js' %}"></script>
|
||||
```
|
||||
|
||||
**Что делает `chat-list-enhanced.js`:**
|
||||
- ✅ WebSocket подключение для реального времени
|
||||
- ✅ Обновление последних сообщений в списке
|
||||
- ✅ Перемещение чатов в секцию "Закрепленные"
|
||||
- ✅ Контекстное меню (правый клик): закрепить, уведомления, скрыть, прочитать
|
||||
- ✅ Индикаторы печати в списке
|
||||
- ✅ API вызов `togglePinChat()` для закрепления
|
||||
- ✅ Анимации обновления чатов
|
||||
|
||||
---
|
||||
|
||||
## 6. Структура данных чата в шаблоне
|
||||
|
||||
### Доступные атрибуты:
|
||||
```django
|
||||
chat.pk # ID чата
|
||||
chat.type # 'private', 'group', 'department', 'channel', 'announcement', 'global'
|
||||
chat.name # Название (для всех кроме private)
|
||||
chat.description # Описание
|
||||
chat.avatar # ImageField (опционально)
|
||||
chat.participants.all # QuerySet участников (для private)
|
||||
chat.department # ForeignKey (для department)
|
||||
chat.messages.count # Количество сообщений
|
||||
chat.unread_count # Непрочитанные (вычисляется во view)
|
||||
chat.last_message.0 # Последнее сообщение (вычисляется)
|
||||
.created_at
|
||||
.author
|
||||
.content
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Тестирование
|
||||
|
||||
### Чек-лист для проверки:
|
||||
- [ ] Отображение всех 6 секций чатов (закрепленные, глобальный, отделы, личные, группы, каналы, объявления)
|
||||
- [ ] Аватары показываются корректно
|
||||
- [ ] Фильтр по типу чата работает
|
||||
- [ ] Поиск находит чаты по названию/участникам
|
||||
- [ ] Модал создания:
|
||||
- [ ] Переключение типа чата меняет отображаемые поля
|
||||
- [ ] Для личного чата: выбор 1 собеседника → редирект на `/start_private_chat/`
|
||||
- [ ] Для группы: выбор участников, название, описание → POST `/api/chat/create/`
|
||||
- [ ] Для канала/объявлений: только название, описание → POST `/api/chat/create/`
|
||||
- [ ] RecipientPicker:
|
||||
- [ ] Ограничение выбора работает (1 для private, 100 для group)
|
||||
- [ ] `getSelected()` возвращает массив объектов
|
||||
- [ ] `setMaxSelections()` корректно обрезает выбор
|
||||
- [ ] WebSocket обновления (`chat-list-enhanced.js`):
|
||||
- [ ] Последние сообщения обновляются в реальном времени
|
||||
- [ ] Закрепление чата работает
|
||||
- [ ] Контекстное меню открывается
|
||||
- [ ] Индикаторы печати отображаются
|
||||
|
||||
---
|
||||
|
||||
## 8. Следующие шаги
|
||||
|
||||
1. **Обновить `chat_detail.html`:**
|
||||
- Подключить `chat-detail-enhanced.js`
|
||||
- Добавить UI для реакций, ответов, пересылки
|
||||
- Добавить редактирование/удаление сообщений
|
||||
- Добавить быстрые реакции на hover
|
||||
|
||||
2. **Создать view для API `/api/chat/create/`:**
|
||||
- Проверить, что endpoint существует в `api_views.py`
|
||||
- Убедиться в правильной авторизации
|
||||
- Добавить создание ChatMembership с ролью 'owner' для создателя
|
||||
|
||||
3. **Тестирование интеграции:**
|
||||
- Создание чатов всех типов
|
||||
- Отправка сообщений
|
||||
- Real-time обновления через WebSocket
|
||||
|
||||
4. **Оптимизация:**
|
||||
- Добавить пагинацию для больших списков чатов
|
||||
- Кеширование аватаров
|
||||
- Lazy loading для секций
|
||||
|
||||
---
|
||||
|
||||
## 9. API Endpoints используемые в шаблоне
|
||||
|
||||
### Существующие:
|
||||
- `POST /communications/start_private_chat/<user_id>/` - создание личного чата (старый endpoint)
|
||||
- `GET /api/v1/employees/` - список сотрудников для RecipientPicker
|
||||
|
||||
### Новые (из api_views.py):
|
||||
- `POST /communications/api/chat/create/` - создание group/channel/announcement
|
||||
```json
|
||||
{
|
||||
"type": "group",
|
||||
"name": "Название группы",
|
||||
"description": "Описание",
|
||||
"participant_ids": [1, 2, 3]
|
||||
}
|
||||
```
|
||||
|
||||
- `POST /communications/api/chat/<chat_id>/pin/` - закрепление чата
|
||||
```json
|
||||
{
|
||||
"pinned": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Известные ограничения
|
||||
|
||||
1. **Глобальный чат:** может быть только один, создается вручную через админку
|
||||
2. **Департаментные чаты:** привязаны к `Department`, создаются автоматически
|
||||
3. **Аватары:** размер файла ограничен настройками Django `FILE_UPLOAD_MAX_MEMORY_SIZE`
|
||||
4. **RecipientPicker:** поддерживает только сотрудников из `/api/v1/employees/`, не внешних пользователей
|
||||
|
||||
---
|
||||
|
||||
## Заключение
|
||||
|
||||
Шаблон `chat_list.html` и модал создания теперь полностью поддерживают:
|
||||
- ✅ 6 типов чатов (private, group, department, channel, announcement, global)
|
||||
- ✅ Закрепленные чаты
|
||||
- ✅ Аватары чатов
|
||||
- ✅ Гибкое создание с разными полями в зависимости от типа
|
||||
- ✅ Фильтрация и поиск
|
||||
- ✅ Подготовку для real-time обновлений через WebSocket
|
||||
|
||||
Следующий шаг - обновление `chat_detail.html` для отображения расширенных возможностей сообщений.
|
||||
@ -1,739 +0,0 @@
|
||||
# Система уведомлений - Этап 1 ЗАВЕРШЕН ✅
|
||||
|
||||
## Дата завершения: 20 ноября 2025 г.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Результаты
|
||||
|
||||
### ✅ Создано приложение notifications
|
||||
- Django app с полной структурой
|
||||
- Интегрировано в проект EUSRR
|
||||
|
||||
### ✅ База данных (5 моделей)
|
||||
1. **NotificationCategory** - 8 категорий
|
||||
2. **NotificationType** - 35 типов уведомлений
|
||||
3. **Notification** - основная модель с GenericForeignKey
|
||||
4. **UserNotificationSettings** - персональные настройки
|
||||
5. **NotificationTemplate** - шаблоны для каналов
|
||||
|
||||
**Миграции:** Созданы и применены ✅
|
||||
|
||||
### ✅ Django Admin
|
||||
- 5 ModelAdmin с расширенными фильтрами
|
||||
- Визуализация каналов с эмодзи: 🌐📧✈️💬💚
|
||||
- Actions: mark_as_read, mark_as_unread, archive
|
||||
|
||||
### ✅ 35 типов уведомлений в 8 категориях
|
||||
|
||||
**💬 Коммуникации (4):**
|
||||
- chat_new_message
|
||||
- chat_mention
|
||||
- chat_reply
|
||||
- chat_added_to_chat
|
||||
|
||||
**📄 Документы (4):**
|
||||
- document_ready
|
||||
- document_signed_all
|
||||
- document_reminder
|
||||
- document_comment
|
||||
|
||||
**📋 Заявления (5):**
|
||||
- request_new
|
||||
- request_approved
|
||||
- request_rejected
|
||||
- request_comment
|
||||
- request_status_changed
|
||||
|
||||
**📅 Календарь (5):**
|
||||
- event_created
|
||||
- event_reminder_hour
|
||||
- event_reminder_day
|
||||
- event_changed
|
||||
- event_cancelled
|
||||
|
||||
**👥 Отдел (5):**
|
||||
- department_new_employee
|
||||
- department_employee_left
|
||||
- department_structure_changed
|
||||
- department_new_head
|
||||
- department_announcement
|
||||
|
||||
**👤 Профиль (5):**
|
||||
- profile_data_changed
|
||||
- profile_password_changed
|
||||
- profile_email_changed
|
||||
- profile_messenger_linked
|
||||
- profile_new_login
|
||||
|
||||
**📰 Новости (3):**
|
||||
- feed_new_post
|
||||
- feed_post_comment
|
||||
- feed_post_reaction
|
||||
|
||||
**⚙️ Система (4):**
|
||||
- system_maintenance
|
||||
- system_announcement
|
||||
- system_new_feature
|
||||
- system_policy_change
|
||||
|
||||
### ✅ WebSocket инфраструктура
|
||||
- **NotificationConsumer** для real-time
|
||||
- Маршрут: `ws://host/ws/notifications/`
|
||||
- Интегрирован в `eusrr_backend/asgi.py`
|
||||
- События:
|
||||
- `unread_count` - текущее количество
|
||||
- `new_notification` - новое уведомление
|
||||
- `notification_count_update` - обновление счетчика
|
||||
|
||||
### ✅ Сервисный слой (NotificationService)
|
||||
|
||||
**Методы:**
|
||||
- `create_notification()` - создать и отправить
|
||||
- `send_notification()` - отправить по каналам
|
||||
- `send_web_notification()` - WebSocket отправка
|
||||
- `get_user_settings()` - получить/создать настройки
|
||||
- `mark_as_read()` - отметить прочитанным
|
||||
- `mark_all_as_read()` - все прочитанными
|
||||
|
||||
### ✅ REST API (9 endpoints)
|
||||
1. `GET /api/notifications/` - список с пагинацией
|
||||
2. `GET /api/notifications/count/` - счетчик непрочитанных
|
||||
3. `POST /api/notifications/<id>/read/` - отметить прочитанным
|
||||
4. `POST /api/notifications/read-all/` - все прочитанными
|
||||
5. `DELETE /api/notifications/<id>/` - удалить (архивировать)
|
||||
6. `GET /api/notifications/categories/` - список категорий
|
||||
7. `GET /api/notifications/settings/` - настройки пользователя
|
||||
8. `PUT /api/notifications/settings/update/` - обновить настройки
|
||||
|
||||
### ✅ Frontend компоненты
|
||||
|
||||
#### JavaScript (notification-manager.js)
|
||||
- **NotificationManager** класс
|
||||
- WebSocket подключение с автореконнектом
|
||||
- Обновление badge в real-time
|
||||
- Toast-уведомления
|
||||
- Загрузка и отображение списка
|
||||
- API интеграция
|
||||
- Обработка событий
|
||||
|
||||
**Функции:**
|
||||
- `connectWebSocket()` - подключение
|
||||
- `handleMessage()` - обработка сообщений
|
||||
- `updateBadge()` - обновление счетчика
|
||||
- `loadInitialNotifications()` - загрузка списка
|
||||
- `renderNotifications()` - отображение
|
||||
- `showNewNotification()` - новое уведомление
|
||||
- `showToast()` - toast уведомление
|
||||
- `markAsRead()` - отметить прочитанным
|
||||
- `getTimeAgo()` - относительное время
|
||||
|
||||
#### CSS (notifications.css)
|
||||
- Стили для badge в navbar
|
||||
- Стили для dropdown списка
|
||||
- Toast уведомления
|
||||
- Анимации (slideIn, bellRing)
|
||||
- Приоритеты и категории
|
||||
- Адаптивный дизайн
|
||||
|
||||
### ✅ UI интеграция
|
||||
|
||||
#### navbar.html
|
||||
- Колокольчик с badge (скрыт при 0)
|
||||
- Dropdown с уведомлениями
|
||||
- Список последних 5 уведомлений
|
||||
- Ссылки на полный список и настройки
|
||||
- Раздел мессенджеров
|
||||
|
||||
#### notification_list.html
|
||||
- Полная страница уведомлений
|
||||
- Фильтры по категориям
|
||||
- Кнопка "Отметить все прочитанными"
|
||||
- Placeholder для будущего функционала
|
||||
|
||||
#### notification_settings.html
|
||||
- Страница настроек
|
||||
- Список всех категорий
|
||||
- Описание каждой категории
|
||||
- Информация о будущей реализации
|
||||
|
||||
### ✅ Management команда
|
||||
```bash
|
||||
python manage.py create_notification_types
|
||||
```
|
||||
- Создает 8 категорий
|
||||
- Создает 35 типов
|
||||
- Idempotent (можно запускать повторно)
|
||||
|
||||
---
|
||||
|
||||
## 📁 Структура файлов
|
||||
|
||||
```
|
||||
backend/
|
||||
├── notifications/
|
||||
│ ├── __init__.py
|
||||
│ ├── admin.py ✅ 5 ModelAdmin (260 строк)
|
||||
│ ├── apps.py ✅ Auto-import signals
|
||||
│ ├── consumers.py ✅ WebSocket consumer (122 строки)
|
||||
│ ├── models.py ✅ 5 models (459 строк)
|
||||
│ ├── routing.py ✅ WebSocket routing
|
||||
│ ├── services.py ✅ NotificationService (300 строк)
|
||||
│ ├── signals.py ✅ Placeholder
|
||||
│ ├── urls.py ✅ 2 URL patterns
|
||||
│ ├── views.py ✅ 2 views
|
||||
│ ├── api_views.py ✅ 8 API endpoints (224 строки)
|
||||
│ ├── api_urls.py ✅ 9 API routes
|
||||
│ ├── migrations/
|
||||
│ │ └── 0001_initial.py ✅ Initial migration
|
||||
│ └── management/commands/
|
||||
│ └── create_notification_types.py ✅ (562 строки)
|
||||
│
|
||||
├── templates/
|
||||
│ ├── base.html ✅ Подключены CSS/JS
|
||||
│ ├── includes/
|
||||
│ │ └── navbar.html ✅ Обновлен dropdown
|
||||
│ └── notifications/
|
||||
│ ├── notification_list.html ✅ Страница списка
|
||||
│ └── notification_settings.html ✅ Страница настроек
|
||||
│
|
||||
├── static/
|
||||
│ ├── css/notifications/
|
||||
│ │ └── notifications.css ✅ Полные стили (180 строк)
|
||||
│ └── js/notifications/
|
||||
│ └── notification-manager.js ✅ Manager класс (350 строк)
|
||||
│
|
||||
├── eusrr_backend/
|
||||
│ ├── asgi.py ✅ WebSocket routing
|
||||
│ ├── urls.py ✅ Notifications URLs
|
||||
│ └── settings.py ✅ INSTALLED_APPS
|
||||
│
|
||||
├── api/
|
||||
│ └── urls.py ✅ API routing
|
||||
│
|
||||
├── test_notifications.py ✅ Тестовый скрипт
|
||||
├── NOTIFICATION_SYSTEM_PLAN.md ✅ План (2100 строк)
|
||||
└── NOTIFICATION_STAGE1_COMPLETE.md ✅ Отчет этапа 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
### ✅ Проверка проекта
|
||||
```bash
|
||||
python manage.py check
|
||||
# System check identified no issues (0 silenced).
|
||||
```
|
||||
|
||||
### ✅ Создание данных
|
||||
```bash
|
||||
python manage.py create_notification_types
|
||||
# ✓ Создано категорий: 8
|
||||
# ✓ Создано типов: 35
|
||||
```
|
||||
|
||||
### ✅ Тестовый скрипт
|
||||
```bash
|
||||
python test_notifications.py
|
||||
# ✅ Уведомление создано: ID=1
|
||||
# ✅ Отправлено на веб: True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Статистика
|
||||
|
||||
### Код
|
||||
- **Python файлов:** 12
|
||||
- **Строк Python:** ~2400+
|
||||
- **JavaScript:** 350 строк
|
||||
- **CSS:** 180 строк
|
||||
- **HTML шаблонов:** 3 обновлено, 2 создано
|
||||
- **Миграций:** 1
|
||||
|
||||
### База данных
|
||||
- **Таблиц:** 5
|
||||
- **Индексов:** 8
|
||||
- **Категорий:** 8
|
||||
- **Типов уведомлений:** 35
|
||||
|
||||
### API
|
||||
- **REST endpoints:** 9
|
||||
- **WebSocket маршрутов:** 1
|
||||
- **URL patterns:** 11
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Как использовать
|
||||
|
||||
### 1. Создать уведомление в коде
|
||||
|
||||
```python
|
||||
from notifications.services import NotificationService
|
||||
|
||||
# Простое уведомление
|
||||
notification = NotificationService.create_notification(
|
||||
recipient=user,
|
||||
notification_type_code='chat_new_message',
|
||||
title='Новое сообщение',
|
||||
message='У вас новое сообщение в чате "Общий"',
|
||||
action_url='/communications/chat/123/',
|
||||
)
|
||||
|
||||
# С привязкой к объекту
|
||||
NotificationService.create_notification(
|
||||
recipient=user,
|
||||
notification_type_code='document_ready',
|
||||
title='Документ на ознакомление',
|
||||
message=f'Документ "{document.title}" требует ознакомления',
|
||||
content_object=document,
|
||||
action_url=f'/documents/{document.id}/',
|
||||
metadata={'deadline': document.deadline.isoformat()},
|
||||
)
|
||||
```
|
||||
|
||||
### 2. WebSocket подключение
|
||||
|
||||
```javascript
|
||||
// Автоматически при загрузке страницы
|
||||
// Подключается к ws://host/ws/notifications/
|
||||
|
||||
// Доступ к менеджеру
|
||||
window.notificationManager.markAsRead(notificationId);
|
||||
window.notificationManager.markAllAsRead();
|
||||
```
|
||||
|
||||
### 3. API запросы
|
||||
|
||||
```bash
|
||||
# Получить список
|
||||
GET /api/notifications/?page=1&page_size=20
|
||||
|
||||
# Получить счетчик
|
||||
GET /api/notifications/count/
|
||||
|
||||
# Отметить прочитанным
|
||||
POST /api/notifications/123/read/
|
||||
|
||||
# Все прочитанными
|
||||
POST /api/notifications/read-all/
|
||||
```
|
||||
|
||||
### 4. Просмотр в Admin
|
||||
|
||||
```
|
||||
http://localhost:9000/admin/notifications/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI Features
|
||||
|
||||
### Navbar
|
||||
- ✅ Колокольчик с badge (скрыт при 0 уведомлений)
|
||||
- ✅ Dropdown с последними 5 уведомлениями
|
||||
- ✅ Ссылка "Все" на полный список
|
||||
- ✅ Раздел мессенджеров
|
||||
- ✅ Кнопка настроек
|
||||
|
||||
### Toast уведомления
|
||||
- ✅ Автоматическое появление при новом уведомлении
|
||||
- ✅ Цвет по категории
|
||||
- ✅ Автозакрытие через 5 секунд
|
||||
- ✅ Кнопка закрытия
|
||||
- ✅ Анимация slideIn
|
||||
|
||||
### Real-time обновления
|
||||
- ✅ Badge обновляется мгновенно
|
||||
- ✅ Список обновляется при новых уведомлениях
|
||||
- ✅ WebSocket с автореконнектом
|
||||
- ✅ Индикация отключения (красный badge)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Технологии
|
||||
|
||||
### Backend
|
||||
- Django 5.2.4
|
||||
- Django Channels (WebSocket)
|
||||
- PostgreSQL/SQLite
|
||||
- REST Framework
|
||||
|
||||
### Frontend
|
||||
- Vanilla JavaScript (ES6+)
|
||||
- Bootstrap 5
|
||||
- Bootstrap Icons
|
||||
- WebSocket API
|
||||
|
||||
### Интеграции
|
||||
- ✅ ASGI для WebSocket
|
||||
- ✅ Channels Layer для pub/sub
|
||||
- ✅ GenericForeignKey для связей
|
||||
- ✅ JSON fields для метаданных
|
||||
|
||||
---
|
||||
|
||||
## ✨ Возможности
|
||||
|
||||
### Реализовано в Этапе 1
|
||||
- ✅ 35 типов уведомлений
|
||||
- ✅ 8 категорий
|
||||
- ✅ WebSocket real-time
|
||||
- ✅ Персональные настройки
|
||||
- ✅ Множественные каналы (веб готов)
|
||||
- ✅ Приоритеты (4 уровня)
|
||||
- ✅ Группировка
|
||||
- ✅ Архивирование
|
||||
- ✅ Toast уведомления
|
||||
- ✅ Dropdown в navbar
|
||||
- ✅ Полная страница списка
|
||||
- ✅ Страница настроек
|
||||
- ✅ REST API
|
||||
- ✅ Django Admin
|
||||
|
||||
### Будет реализовано дальше
|
||||
- ⏳ Email рассылка (Этап 7)
|
||||
- ⏳ Telegram бот (Этап 8)
|
||||
- ⏳ WhatsApp интеграция
|
||||
- ⏳ WeChat интеграция
|
||||
- ⏳ Celery задачи
|
||||
- ⏳ Дайджесты
|
||||
- ⏳ Звуковые уведомления
|
||||
- ⏳ Push уведомления (PWA)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Следующие шаги
|
||||
|
||||
### ⏸️ Этап 2: Email и Telegram каналы (ОТЛОЖЕН)
|
||||
**Статус: ОТЛОЖЕН НА ПОТОМ**
|
||||
**Дата решения: 20 ноября 2025 г.**
|
||||
|
||||
- ⏸️ EmailNotificationSender
|
||||
- ⏸️ TelegramNotificationSender
|
||||
- ⏸️ Celery tasks для асинхронной отправки
|
||||
- ⏸️ Система шаблонов
|
||||
- ⏸️ Rate limiting
|
||||
|
||||
### 🚀 Этап 3: Интеграция с модулями (ТЕКУЩИЙ)
|
||||
**Статус: В РАБОТЕ**
|
||||
**Приоритет: ВЫСОКИЙ**
|
||||
**Дата начала: 20 ноября 2025 г.**
|
||||
|
||||
Создать signals для автоматической генерации уведомлений:
|
||||
- [x] **Communications signals** (приоритет 1) ✅ ЗАВЕРШЕНО
|
||||
- [x] Новое сообщение в чате → `chat_new_message`
|
||||
- [x] Упоминание (@username) → `chat_mention`
|
||||
- [x] Ответ на сообщение → `chat_reply`
|
||||
- [x] Добавление в чат → `chat_added_to_chat`
|
||||
|
||||
**Файлы:**
|
||||
- `communications/notification_signals.py` - 4 signal handlers
|
||||
- `communications/apps.py` - подключение signals
|
||||
|
||||
**Функционал:**
|
||||
- Извлечение упоминаний через regex (@username)
|
||||
- Проверка участников чата
|
||||
- Различная логика для приватных/групповых чатов
|
||||
- Исключение дубликатов уведомлений
|
||||
|
||||
- [x] **Requests signals** (приоритет 2) ✅ ЗАВЕРШЕНО
|
||||
- [x] Новая заявка → `request_new`
|
||||
- [x] Одобрение → `request_approved`
|
||||
- [x] Отклонение → `request_rejected`
|
||||
- [x] Комментарий → `request_comment`
|
||||
- [x] Изменение статуса → `request_status_changed`
|
||||
|
||||
**Файлы:**
|
||||
- `requests_app/notification_signals.py` - 3 signal handlers
|
||||
- `requests_app/apps.py` - подключение signals
|
||||
|
||||
**Функционал:**
|
||||
- Отслеживание изменения статуса (pre_save + post_save)
|
||||
- Уведомление руководителей и ответственных
|
||||
- Уведомление при комментариях
|
||||
- Приоритет "high" для одобрения/отклонения
|
||||
|
||||
- [x] **Documents signals** (приоритет 3) ✅ ЗАВЕРШЕНО
|
||||
- [x] Новый документ на ознакомление → `document_ready`
|
||||
- [x] Все ознакомились → `document_signed_all`
|
||||
- [x] M2M уведомления для конкретных получателей
|
||||
|
||||
**Файлы:**
|
||||
- `documents/notification_signals.py` - 3 signal handlers
|
||||
- `documents/apps.py` - подключение signals
|
||||
|
||||
**Функционал:**
|
||||
- Поддержка sent_to_all и конкретных получателей
|
||||
- Проверка полного ознакомления
|
||||
- Приоритет "high" для новых документов
|
||||
|
||||
- [x] **Calendar signals** (приоритет 4) ✅ ЗАВЕРШЕНО
|
||||
- [x] Новое событие → `event_created`
|
||||
- [x] Изменение события → `event_changed`
|
||||
- [x] Отмена события → `event_cancelled`
|
||||
- [ ] Напоминания → будут через Celery tasks
|
||||
|
||||
**Файлы:**
|
||||
- `calendar_app/notification_signals.py` - 3 signal handlers
|
||||
- `calendar_app/apps.py` - подключение signals
|
||||
|
||||
**Функционал:**
|
||||
- Поддержка событий компании и отдела
|
||||
- Отслеживание изменений важных полей
|
||||
- Уведомления всем участникам
|
||||
|
||||
- [x] **Feed signals** (приоритет 5) ✅ ЗАВЕРШЕНО
|
||||
- [x] Новая публикация → `feed_new_post`
|
||||
- [x] Комментарий → `feed_post_comment`
|
||||
- [x] Реакция (лайк) → `feed_post_reaction` (вызов из view)
|
||||
|
||||
**Файлы:**
|
||||
- `feed/notification_signals.py` - 2 signal handlers + функция для лайков
|
||||
- `feed/apps.py` - подключение signals
|
||||
|
||||
**Функционал:**
|
||||
- Разные типы публикаций (компания/отдел/личная)
|
||||
- Автоматические уведомления при комментариях
|
||||
- notify_post_reaction() для вызова из views
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Этап 3 ПОЛНОСТЬЮ ЗАВЕРШЕН И ПРОТЕСТИРОВАН!
|
||||
|
||||
**Дата завершения:** 20 ноября 2025 г.
|
||||
|
||||
### 🧪 Результаты тестирования:
|
||||
|
||||
**✅ ВСЕ 5 МОДУЛЕЙ ПРОШЛИ ТЕСТЫ УСПЕШНО!**
|
||||
|
||||
Создан тестовый скрипт `test_all_signals.py` (280 строк) для комплексной проверки.
|
||||
|
||||
**Результаты теста:**
|
||||
```
|
||||
======================================================================
|
||||
ПОЛНЫЙ ТЕСТ ВСЕХ NOTIFICATION SIGNALS
|
||||
======================================================================
|
||||
|
||||
[1/5] COMMUNICATIONS: Сообщения и упоминания
|
||||
new_message: 1 ✅
|
||||
mention: 1 ✅
|
||||
reply: 1 ✅
|
||||
ПРОЙДЕНО ✅
|
||||
|
||||
[2/5] REQUESTS: Заявки и согласования
|
||||
new_request: 0 (нормально - нет прав)
|
||||
comment: 1 ✅
|
||||
approved: 1 ✅
|
||||
ПРОЙДЕНО ✅
|
||||
|
||||
[3/5] DOCUMENTS: Документы и подтверждения
|
||||
document_ready: 1 ✅
|
||||
document_signed_all: 1 ✅
|
||||
ПРОЙДЕНО ✅
|
||||
|
||||
[4/5] CALENDAR: События и изменения
|
||||
event_created: 2 ✅
|
||||
event_changed: 2 ✅
|
||||
event_cancelled: 2 ✅
|
||||
ПРОЙДЕНО ✅
|
||||
|
||||
[5/5] FEED: Посты и комментарии
|
||||
feed_new_post: 1 ✅
|
||||
feed_post_comment: 1 ✅
|
||||
feed_post_reaction: 1 ✅
|
||||
ПРОЙДЕНО ✅
|
||||
|
||||
======================================================================
|
||||
Всего создано уведомлений: 19
|
||||
Распределение по 15 различным типам
|
||||
ВСЕ 5 МОДУЛЕЙ ПРОТЕСТИРОВАНЫ УСПЕШНО!
|
||||
======================================================================
|
||||
```
|
||||
|
||||
### Статистика интеграции:
|
||||
|
||||
- **Модулей интегрировано:** 5/5 (100%) ✅
|
||||
- **Signal handlers создано:** 15 ✅
|
||||
- **Типов уведомлений работает:** 15/35 (43%)
|
||||
- **Строк кода signals:** ~950
|
||||
- **Строк тестового кода:** 280
|
||||
- **Уведомлений создано в тесте:** 19
|
||||
|
||||
### Созданные файлы:
|
||||
|
||||
1. `communications/notification_signals.py` - 200+ строк ✅ ПРОТЕСТИРОВАНО
|
||||
2. `requests_app/notification_signals.py` - 230+ строк ✅ ПРОТЕСТИРОВАНО
|
||||
3. `documents/notification_signals.py` - 160+ строк ✅ ПРОТЕСТИРОВАНО
|
||||
4. `calendar_app/notification_signals.py` - 180+ строк ✅ ПРОТЕСТИРОВАНО
|
||||
5. `feed/notification_signals.py` - 170+ строк ✅ ПРОТЕСТИРОВАНО
|
||||
6. `test_all_signals.py` - 280 строк (комплексный тест)
|
||||
7. `quick_test.py` - 110 строк (быстрый тест)
|
||||
|
||||
### Обработка событий:
|
||||
|
||||
**Communications (4 типа):** ✅ РАБОТАЕТ
|
||||
- ✅ chat_new_message - Новое сообщение в чате
|
||||
- ✅ chat_mention - Упоминание @email (regex: `r'@([\w.+-]+@[\w.-]+\.[\w]+)'`)
|
||||
- ✅ chat_reply - Ответ на сообщение
|
||||
- ✅ chat_added_to_chat - Добавление в чат
|
||||
|
||||
**Requests (5 типов):** ✅ РАБОТАЕТ
|
||||
- ✅ request_new - Новая заявка (руководителям + approvers)
|
||||
- ✅ request_approved - Одобрение заявки
|
||||
- ✅ request_rejected - Отклонение заявки
|
||||
- ✅ request_comment - Комментарий к заявке
|
||||
- ✅ request_status_changed - Изменение статуса
|
||||
|
||||
**Documents (2 типа):** ✅ РАБОТАЕТ
|
||||
- ✅ document_ready - Новый документ на ознакомление
|
||||
- ✅ document_signed_all - Все ознакомились
|
||||
|
||||
**Calendar (3 типа):** ✅ РАБОТАЕТ
|
||||
- ✅ event_created - Новое событие
|
||||
- ✅ event_changed - Изменение события (title, dates, location)
|
||||
- ✅ event_cancelled - Отмена события
|
||||
- ⏳ event_reminder_* - Напоминания (через Celery tasks)
|
||||
|
||||
**Feed (3 типа):** ✅ РАБОТАЕТ
|
||||
- ✅ feed_new_post - Новая публикация
|
||||
- ✅ feed_post_comment - Комментарий к посту
|
||||
- ✅ feed_post_reaction - Реакция на пост (вызов через `notify_post_reaction()`)
|
||||
|
||||
### Технические решения:
|
||||
|
||||
- **pre_save + post_save** для отслеживания изменений ✅
|
||||
- **m2m_changed** для многие-ко-многим связей ✅
|
||||
- **pre_delete** для уведомлений об удалении ✅
|
||||
- **Исключение дубликатов** уведомлений ✅
|
||||
- **Контекстные сообщения** в зависимости от типа ✅
|
||||
- **Метаданные** для каждого уведомления ✅
|
||||
- **GenericForeignKey** для связи с объектами ✅
|
||||
|
||||
### Исправления при тестировании:
|
||||
|
||||
1. **Удален несуществующий параметр `priority`** из NotificationService.create_notification()
|
||||
- Исправлено в: requests_app, documents, calendar_app
|
||||
|
||||
2. **Исправлена regex для упоминаний** в communications
|
||||
- Было: `r'@([\w.]+)'` (username)
|
||||
- Стало: `r'@([\w.+-]+@[\w.-]+\.[\w]+)'` (email)
|
||||
|
||||
3. **Исправлены названия полей моделей:**
|
||||
- Request: `request_type` → `type`, `start_date` → `date_from`, `end_date` → `date_to`
|
||||
- Document: `author` → `uploaded_by`
|
||||
- DocumentAcknowledgement: `employee` → `user`, убрано `acknowledged`
|
||||
- CalendarEvent: datetime → separate date + time fields
|
||||
- Post: `content` → `body`, `post_type` → `type`
|
||||
- Comment: `content` → `text`
|
||||
|
||||
---
|
||||
|
||||
### Этап 4: UI улучшения (СЛЕДУЮЩИЙ)
|
||||
- Полнофункциональный список с пагинацией
|
||||
- Фильтрация и поиск
|
||||
- Настройки с чекбоксами
|
||||
- Тихий режим UI
|
||||
|
||||
---
|
||||
|
||||
## 🎉 ОБНОВЛЕНИЕ: Этап 4 ЗАВЕРШЕН! (20 ноября 2025)
|
||||
|
||||
### ✅ Реализовано в Этапе 4:
|
||||
|
||||
**1. Звуковые уведомления** 🔊
|
||||
- Web Audio API (приятный звук 800 Hz, 300ms)
|
||||
- Переключатель включения/выключения
|
||||
- Сохранение в localStorage
|
||||
- Автовоспроизведение при новом уведомлении
|
||||
|
||||
**2. Расширенный список уведомлений** 📋
|
||||
- Фильтры: категория, статус, текстовый поиск
|
||||
- Пагинация с умной навигацией
|
||||
- Действия: прочитать, удалить, перейти
|
||||
- Относительное время ("2 ч назад")
|
||||
- Файл: `notification-list.js` (470 строк)
|
||||
|
||||
**3. Функциональные настройки** ⚙️
|
||||
- 8 категорий с переключателями
|
||||
- Каналы: Веб/Email/Telegram
|
||||
- Автосохранение при изменении
|
||||
- Toast-уведомления
|
||||
- Файл: `notification-settings.js` (280 строк)
|
||||
|
||||
**4. API улучшения**
|
||||
- Поддержка фильтрации в get_notifications
|
||||
- Поиск по заголовку и тексту (Q lookup)
|
||||
- Новый endpoint: update_category_settings
|
||||
- Оптимизация с select_related
|
||||
|
||||
**5. Тестовый генератор**
|
||||
- `generate_notification.py` - интерактивный скрипт
|
||||
- 7 типов уведомлений
|
||||
- Для проверки WebSocket/звука/браузерных уведомлений
|
||||
|
||||
### Новые файлы Этапа 4:
|
||||
```
|
||||
static/js/notifications/
|
||||
├── notification-list.js ✅ 470 строк
|
||||
└── notification-settings.js ✅ 280 строк
|
||||
|
||||
templates/notifications/
|
||||
├── notification_list_new.html ✅ Полный функционал
|
||||
└── notification_settings_new.html ✅ Рабочие настройки
|
||||
|
||||
backend/
|
||||
├── generate_notification.py ✅ Тестовый генератор
|
||||
└── NOTIFICATION_STAGE4_COMPLETE.md ✅ Документация
|
||||
|
||||
Обновлены:
|
||||
- views.py (используют новые шаблоны)
|
||||
- api_views.py (фильтрация, поиск, категории)
|
||||
- notification-manager.js (звук)
|
||||
```
|
||||
|
||||
### Статистика Этапа 4:
|
||||
- JavaScript: +750 строк
|
||||
- API endpoints: +1
|
||||
- Новых файлов: 6
|
||||
- Обновленных файлов: 3
|
||||
|
||||
---
|
||||
|
||||
## ✅ Критерии готовности Этапа 1
|
||||
|
||||
- [x] Приложение создано и зарегистрировано
|
||||
- [x] 5 моделей с миграциями
|
||||
- [x] Django Admin настроен
|
||||
- [x] 35 типов уведомлений созданы
|
||||
- [x] WebSocket consumer работает
|
||||
- [x] Сервисный слой реализован
|
||||
- [x] REST API создан
|
||||
- [x] Frontend JavaScript готов
|
||||
- [x] CSS стили добавлены
|
||||
- [x] Navbar обновлен
|
||||
- [x] Страницы созданы
|
||||
- [x] Тестирование пройдено
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Заключение
|
||||
|
||||
**Этап 1 ПОЛНОСТЬЮ ЗАВЕРШЕН!**
|
||||
|
||||
Создана рабочая система уведомлений с:
|
||||
- ✅ Полной backend инфраструктурой
|
||||
- ✅ Real-time WebSocket подключением
|
||||
- ✅ REST API для всех операций
|
||||
- ✅ Современным frontend интерфейсом
|
||||
- ✅ 35 готовыми типами уведомлений
|
||||
- ✅ Расширяемой архитектурой
|
||||
|
||||
**Система готова к использованию и дальнейшему развитию!** 🚀
|
||||
|
||||
---
|
||||
|
||||
*Дата завершения: 20 ноября 2025 г.*
|
||||
*Время разработки: 1 сессия*
|
||||
*Автор: GitHub Copilot + User*
|
||||
@ -1,427 +0,0 @@
|
||||
# Этап 1: Базовая инфраструктура - ЗАВЕРШЕН ✅
|
||||
|
||||
## Дата: 20 ноября 2025
|
||||
## Статус: УСПЕШНО ЗАВЕРШЕН
|
||||
|
||||
---
|
||||
|
||||
## Выполненные задачи
|
||||
|
||||
### ✅ 1. Создано Django приложение `notifications`
|
||||
- Приложение создано командой `python manage.py startapp notifications`
|
||||
- Настроен `apps.py` с автозагрузкой сигналов
|
||||
- Добавлено в `INSTALLED_APPS` в `settings.py`
|
||||
|
||||
### ✅ 2. Созданы 5 моделей базы данных
|
||||
|
||||
#### 2.1 NotificationCategory
|
||||
- 8 категорий: коммуникации, документы, заявки, календарь, отдел, профиль, новости, система
|
||||
- Поля: code, name, description, icon, color, is_active, order
|
||||
- Иконки Bootstrap Icons
|
||||
- Цветовая схема Bootstrap
|
||||
|
||||
#### 2.2 NotificationType
|
||||
- 35 типов уведомлений распределены по категориям
|
||||
- Настройки по умолчанию: каналы доставки, приоритет
|
||||
- Группировка: is_groupable, grouping_window_minutes
|
||||
- Обязательные уведомления: is_required
|
||||
|
||||
#### 2.3 Notification
|
||||
- Основные поля: recipient, notification_type, title, message
|
||||
- GenericForeignKey для связи с любым объектом
|
||||
- Статусы: is_read, is_archived
|
||||
- Каналы доставки: sent_web, sent_email, sent_telegram, sent_whatsapp, sent_wechat
|
||||
- Группировка: group_key, is_grouped, grouped_count
|
||||
- Методы: mark_as_read(), archive()
|
||||
- 4 индекса для оптимизации запросов
|
||||
|
||||
#### 2.4 UserNotificationSettings
|
||||
- Персональные настройки для каждого пользователя
|
||||
- Включение/выключение типов уведомлений
|
||||
- Выбор каналов доставки
|
||||
- Тихий режим: quiet_hours_enabled, quiet_start_time, quiet_end_time
|
||||
- Уникальный ключ: (user, notification_type)
|
||||
|
||||
#### 2.5 NotificationTemplate
|
||||
- Шаблоны для разных каналов: web, email, telegram, whatsapp, wechat
|
||||
- Django template синтаксис
|
||||
- HTML шаблоны для email
|
||||
- Кнопки действий
|
||||
|
||||
### ✅ 3. Миграции базы данных
|
||||
```bash
|
||||
python manage.py makemigrations notifications
|
||||
# Создана миграция: notifications\migrations\0001_initial.py
|
||||
|
||||
python manage.py migrate notifications
|
||||
# Применена миграция: OK
|
||||
```
|
||||
|
||||
### ✅ 4. Django Admin настроен для всех моделей
|
||||
|
||||
#### NotificationCategoryAdmin
|
||||
- Список: name, code, icon_preview, color, order, is_active
|
||||
- Редактируемые inline: order, is_active
|
||||
- Фильтры: is_active
|
||||
- Поиск: name, code, description
|
||||
|
||||
#### NotificationTypeAdmin
|
||||
- Список: name, code, category, priority, default_enabled, is_groupable, is_required, is_active
|
||||
- Фильтры: category, priority, default_enabled, is_groupable, is_required, is_active
|
||||
- Fieldsets: основная информация, настройки по умолчанию, группировка
|
||||
|
||||
#### NotificationAdmin
|
||||
- Список: id, recipient_name, title_short, notification_type, is_read, created_at, channels_sent
|
||||
- Фильтры: is_read, is_archived, category, type, created_at
|
||||
- Поиск: title, message, recipient (first_name, last_name, email)
|
||||
- Date hierarchy: created_at
|
||||
- Actions: mark_as_read, mark_as_unread, archive_notifications
|
||||
- Отображение каналов: 🌐 Веб, 📧 Email, ✈️ Telegram, 💬 WhatsApp, 💚 WeChat
|
||||
|
||||
#### UserNotificationSettingsAdmin
|
||||
- Список: user_name, notification_type, is_enabled, channels_enabled, quiet_hours_status
|
||||
- Фильтры: is_enabled, category, каналы, quiet_hours_enabled
|
||||
- Визуализация: эмодзи для активных каналов, 🌙 для тихого режима
|
||||
|
||||
#### NotificationTemplateAdmin
|
||||
- Список: notification_type, channel, is_active, updated_at
|
||||
- Фильтры: channel, is_active, category
|
||||
- Fieldsets: основная информация, шаблоны, email (дополнительно)
|
||||
|
||||
### ✅ 5. Management команда для начальных данных
|
||||
|
||||
#### `python manage.py create_notification_types`
|
||||
Создано:
|
||||
- **8 категорий** уведомлений
|
||||
- **35 типов** уведомлений
|
||||
|
||||
**Коммуникации (4 типа):**
|
||||
- chat_new_message - Новое сообщение в чате
|
||||
- chat_mention - Упоминание в сообщении (@username)
|
||||
- chat_reply - Ответ на ваше сообщение
|
||||
- chat_added_to_chat - Добавление в чат
|
||||
|
||||
**Документы (4 типа):**
|
||||
- document_ready - Документ на ознакомление
|
||||
- document_signed_all - Документ подписан всеми
|
||||
- document_reminder - Напоминание об ознакомлении
|
||||
- document_comment - Комментарий к документу
|
||||
|
||||
**Заявления (5 типов):**
|
||||
- request_new - Новая заявка
|
||||
- request_approved - Заявка одобрена
|
||||
- request_rejected - Заявка отклонена
|
||||
- request_comment - Комментарий к заявке
|
||||
- request_status_changed - Изменение статуса заявки
|
||||
|
||||
**Календарь (5 типов):**
|
||||
- event_created - Новое событие
|
||||
- event_reminder_hour - Напоминание за час
|
||||
- event_reminder_day - Напоминание за день
|
||||
- event_changed - Изменение события
|
||||
- event_cancelled - Отмена события
|
||||
|
||||
**Отдел (5 типов):**
|
||||
- department_new_employee - Новый сотрудник в отделе
|
||||
- department_employee_left - Сотрудник покинул отдел
|
||||
- department_structure_changed - Изменение структуры отдела
|
||||
- department_new_head - Новый руководитель
|
||||
- department_announcement - Объявление для отдела
|
||||
|
||||
**Профиль (5 типов):**
|
||||
- profile_data_changed - Изменение данных профиля
|
||||
- profile_password_changed - Изменение пароля (обязательное)
|
||||
- profile_email_changed - Изменение email (обязательное)
|
||||
- profile_messenger_linked - Привязка мессенджера
|
||||
- profile_new_login - Вход из нового места (обязательное)
|
||||
|
||||
**Новости (3 типа):**
|
||||
- feed_new_post - Новая новость
|
||||
- feed_post_comment - Комментарий к новости
|
||||
- feed_post_reaction - Реакция на новость
|
||||
|
||||
**Система (4 типа):**
|
||||
- system_maintenance - Технические работы (обязательное)
|
||||
- system_announcement - Важное объявление (обязательное)
|
||||
- system_new_feature - Новый функционал
|
||||
- system_policy_change - Изменение политик (обязательное)
|
||||
|
||||
### ✅ 6. WebSocket инфраструктура
|
||||
|
||||
#### consumers.py - NotificationConsumer
|
||||
- Подключение к группе `notifications_{user.id}`
|
||||
- Отправка текущего счетчика непрочитанных при подключении
|
||||
- Обработка сообщений:
|
||||
- `mark_read` - отметить как прочитанное
|
||||
- `mark_all_read` - отметить все как прочитанные
|
||||
- События:
|
||||
- `notification_new` - новое уведомление
|
||||
- `notification_count_update` - обновление счетчика
|
||||
|
||||
#### routing.py
|
||||
- WebSocket маршрут: `ws/notifications/`
|
||||
- Интегрирован в `eusrr_backend/asgi.py`
|
||||
- Объединен с существующими маршрутами communications
|
||||
|
||||
### ✅ 7. Сервисный слой
|
||||
|
||||
#### services.py - NotificationService
|
||||
**Методы:**
|
||||
- `create_notification()` - создать и отправить уведомление
|
||||
- `send_notification()` - отправить по всем каналам
|
||||
- `send_web_notification()` - отправить через WebSocket
|
||||
- `get_user_settings()` - получить/создать настройки пользователя
|
||||
- `mark_as_read()` - отметить как прочитанное с обновлением счетчика
|
||||
- `mark_all_as_read()` - отметить все как прочитанные
|
||||
|
||||
**Особенности:**
|
||||
- Автоматическое создание настроек пользователя с defaults
|
||||
- Проверка is_enabled перед отправкой
|
||||
- Отправка через channel_layer (async_to_sync)
|
||||
- Обновление счетчика в реальном времени
|
||||
|
||||
### ✅ 8. REST API (9 endpoints)
|
||||
|
||||
#### api_views.py
|
||||
1. `GET /api/notifications/` - список уведомлений с пагинацией
|
||||
- Параметры: page, page_size, category, unread_only
|
||||
2. `GET /api/notifications/count/` - счетчик непрочитанных
|
||||
3. `POST /api/notifications/<id>/read/` - отметить прочитанным
|
||||
4. `POST /api/notifications/read-all/` - отметить все прочитанными
|
||||
5. `DELETE /api/notifications/<id>/` - удалить (архивировать)
|
||||
6. `GET /api/notifications/categories/` - список категорий
|
||||
7. `GET /api/notifications/settings/` - настройки пользователя
|
||||
8. `PUT /api/notifications/settings/update/` - обновить настройки
|
||||
|
||||
#### api_urls.py
|
||||
- Подключен к `api/urls.py`
|
||||
- Доступен по базовому пути `/api/`
|
||||
|
||||
### ✅ 9. URL маршруты
|
||||
|
||||
#### urls.py
|
||||
- `/notifications/` - список уведомлений
|
||||
- `/notifications/settings/` - настройки
|
||||
|
||||
Подключено в `eusrr_backend/urls.py`
|
||||
|
||||
### ✅ 10. Views (заглушки для этапа 4)
|
||||
|
||||
#### views.py
|
||||
- `notification_list()` - страница списка
|
||||
- `notification_settings()` - страница настроек
|
||||
|
||||
### ✅ 11. HTML шаблоны (демо)
|
||||
|
||||
#### notification_list.html
|
||||
- Фильтры по категориям
|
||||
- Кнопка "Отметить все прочитанными"
|
||||
- Базовый JavaScript для демонстрации
|
||||
- WebSocket URL логирование
|
||||
|
||||
#### notification_settings.html
|
||||
- Описание 8 категорий с иконками
|
||||
- Информационное сообщение о следующих этапах
|
||||
- Ссылка назад к списку
|
||||
|
||||
### ✅ 12. Дополнительные файлы
|
||||
|
||||
#### signals.py
|
||||
- Пустой файл для будущих сигналов (этап 5)
|
||||
- Подключен в apps.py через ready()
|
||||
|
||||
---
|
||||
|
||||
## Структура созданных файлов
|
||||
|
||||
```
|
||||
notifications/
|
||||
├── __init__.py
|
||||
├── admin.py ✅ 5 ModelAdmin классов с фильтрами
|
||||
├── apps.py ✅ NotificationsConfig с auto-import signals
|
||||
├── models.py ✅ 5 моделей (459 строк)
|
||||
├── views.py ✅ 2 view функции
|
||||
├── urls.py ✅ 2 URL маршрута
|
||||
├── api_views.py ✅ 8 API endpoint функций
|
||||
├── api_urls.py ✅ 9 API маршрутов
|
||||
├── consumers.py ✅ NotificationConsumer для WebSocket
|
||||
├── routing.py ✅ WebSocket URL patterns
|
||||
├── services.py ✅ NotificationService (244 строки)
|
||||
├── signals.py ✅ Пустой файл для сигналов
|
||||
├── migrations/
|
||||
│ └── 0001_initial.py ✅ Начальная миграция
|
||||
└── management/
|
||||
└── commands/
|
||||
└── create_notification_types.py ✅ 35 типов уведомлений
|
||||
|
||||
templates/notifications/
|
||||
├── notification_list.html ✅ Страница списка
|
||||
└── notification_settings.html ✅ Страница настроек
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Интеграция с проектом
|
||||
|
||||
### settings.py
|
||||
```python
|
||||
INSTALLED_APPS = [
|
||||
...
|
||||
"notifications.apps.NotificationsConfig", # ✅ Добавлено
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### eusrr_backend/urls.py
|
||||
```python
|
||||
urlpatterns = [
|
||||
...
|
||||
path("notifications/", include("notifications.urls", namespace="notifications")), # ✅
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### api/urls.py
|
||||
```python
|
||||
urlpatterns = [
|
||||
...
|
||||
path("", include("notifications.api_urls")), # ✅ Добавлено
|
||||
]
|
||||
```
|
||||
|
||||
### eusrr_backend/asgi.py
|
||||
```python
|
||||
from communications.routing import websocket_urlpatterns as communications_ws
|
||||
from notifications.routing import websocket_urlpatterns as notifications_ws # ✅
|
||||
|
||||
all_websocket_urlpatterns = communications_ws + notifications_ws # ✅
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Проверка работоспособности
|
||||
|
||||
### ✅ Команды выполнены успешно
|
||||
```bash
|
||||
# Создание приложения
|
||||
python manage.py startapp notifications # ✅
|
||||
|
||||
# Миграции
|
||||
python manage.py makemigrations notifications # ✅ 5 моделей
|
||||
python manage.py migrate notifications # ✅ OK
|
||||
|
||||
# Начальные данные
|
||||
python manage.py create_notification_types # ✅ 8 категорий, 35 типов
|
||||
|
||||
# Проверка проекта
|
||||
python manage.py check # ✅ No issues
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Статистика
|
||||
|
||||
### Код
|
||||
- **Python файлов:** 12
|
||||
- **Строк кода:** ~1500+
|
||||
- **Моделей:** 5
|
||||
- **API endpoints:** 9
|
||||
- **WebSocket consumers:** 1
|
||||
- **Management команд:** 1
|
||||
- **HTML шаблонов:** 2
|
||||
- **Миграций:** 1
|
||||
|
||||
### База данных
|
||||
- **Таблиц:** 5
|
||||
- **Индексов:** 8
|
||||
- **Категорий:** 8
|
||||
- **Типов уведомлений:** 35
|
||||
|
||||
### Функционал
|
||||
- ✅ Модели с полной документацией
|
||||
- ✅ Django Admin со всеми фильтрами
|
||||
- ✅ WebSocket real-time соединение
|
||||
- ✅ REST API для всех операций
|
||||
- ✅ Сервисный слой с бизнес-логикой
|
||||
- ✅ Автоматическое создание настроек пользователей
|
||||
- ✅ Группировка уведомлений
|
||||
- ✅ Приоритеты и каналы доставки
|
||||
- ✅ Тихий режим
|
||||
- ✅ Архивирование
|
||||
|
||||
---
|
||||
|
||||
## Следующие шаги (Этап 2-3)
|
||||
|
||||
### Этап 2: Сервисный слой (Week 2-3)
|
||||
- ✅ NotificationService - создан
|
||||
- ⏳ NotificationSender для каждого канала
|
||||
- ⏳ Celery tasks для асинхронной отправки
|
||||
- ⏳ Система шаблонов
|
||||
- ⏳ Продвинутая группировка
|
||||
|
||||
### Этап 3: WebSocket реального времени (Week 3)
|
||||
- ✅ NotificationConsumer - создан
|
||||
- ✅ WebSocket routing - настроен
|
||||
- ⏳ Frontend JavaScript модуль
|
||||
- ⏳ Toast-уведомления
|
||||
- ⏳ Звуковые уведомления
|
||||
- ⏳ Обновление navbar колокольчика
|
||||
|
||||
### Этап 4: UI компоненты (Week 4)
|
||||
- ✅ notification_list.html - базовая версия
|
||||
- ✅ notification_settings.html - базовая версия
|
||||
- ⏳ Полнофункциональный список с подгрузкой
|
||||
- ⏳ Фильтрация и поиск
|
||||
- ⏳ Настройки с чекбоксами
|
||||
- ⏳ Dropdown в navbar
|
||||
|
||||
---
|
||||
|
||||
## Готово к использованию
|
||||
|
||||
Базовая инфраструктура системы уведомлений **полностью готова**. Можно:
|
||||
|
||||
1. **Открыть Django Admin** → Notifications → добавить тестовые уведомления
|
||||
2. **Использовать в коде:**
|
||||
```python
|
||||
from notifications.services import NotificationService
|
||||
|
||||
NotificationService.create_notification(
|
||||
recipient=user,
|
||||
notification_type_code='chat_new_message',
|
||||
title='Тестовое уведомление',
|
||||
message='Проверка системы уведомлений',
|
||||
action_url='/communications/chat/1/',
|
||||
)
|
||||
```
|
||||
3. **WebSocket подключение:**
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://localhost:9000/ws/notifications/');
|
||||
ws.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
console.log('Notification:', data);
|
||||
};
|
||||
```
|
||||
4. **API запросы:**
|
||||
```bash
|
||||
GET /api/notifications/
|
||||
GET /api/notifications/count/
|
||||
POST /api/notifications/1/read/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Заключение
|
||||
|
||||
**Этап 1 успешно завершен за 1 сессию!**
|
||||
|
||||
Создана полноценная база для системы уведомлений:
|
||||
- ✅ Все модели с индексами
|
||||
- ✅ Django Admin с удобными фильтрами
|
||||
- ✅ WebSocket инфраструктура
|
||||
- ✅ REST API
|
||||
- ✅ Сервисный слой
|
||||
- ✅ 35 типов уведомлений готовы к использованию
|
||||
|
||||
**Готово к переходу на Этап 2!** 🚀
|
||||
@ -1,442 +0,0 @@
|
||||
# Этап 4: UI улучшения - ЗАВЕРШЕН ✅
|
||||
|
||||
**Дата завершения:** 20 ноября 2025 г.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Цели этапа
|
||||
|
||||
Улучшить пользовательский интерфейс системы уведомлений:
|
||||
1. ✅ Звуковые уведомления
|
||||
2. ✅ Расширенный список с фильтрами и пагинацией
|
||||
3. ✅ Функциональные настройки с переключателями
|
||||
4. ✅ Улучшенная визуализация
|
||||
|
||||
---
|
||||
|
||||
## ✅ Реализовано
|
||||
|
||||
### 1. Звуковые уведомления
|
||||
|
||||
**Файл:** `static/js/notifications/notification-manager.js`
|
||||
|
||||
**Функционал:**
|
||||
- ✅ Web Audio API для генерации звука
|
||||
- ✅ Настройка включения/выключения звука
|
||||
- ✅ Сохранение предпочтений в localStorage
|
||||
- ✅ Автовоспроизведение при новом уведомлении
|
||||
|
||||
**Код:**
|
||||
```javascript
|
||||
playNotificationSound() {
|
||||
if (!this.soundEnabled) return;
|
||||
|
||||
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
|
||||
oscillator.connect(gainNode);
|
||||
gainNode.connect(audioContext.destination);
|
||||
|
||||
oscillator.frequency.value = 800; // Частота звука
|
||||
oscillator.type = 'sine';
|
||||
|
||||
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
|
||||
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.3);
|
||||
|
||||
oscillator.start(audioContext.currentTime);
|
||||
oscillator.stop(audioContext.currentTime + 0.3);
|
||||
}
|
||||
```
|
||||
|
||||
**API:**
|
||||
- `loadSoundPreference()` - загрузить из localStorage
|
||||
- `saveSoundPreference(enabled)` - сохранить настройку
|
||||
- `playNotificationSound()` - воспроизвести звук
|
||||
|
||||
---
|
||||
|
||||
### 2. Расширенный список уведомлений
|
||||
|
||||
**Файл:** `static/js/notifications/notification-list.js` (470 строк)
|
||||
|
||||
**Функционал:**
|
||||
- ✅ Пагинация с умной навигацией
|
||||
- ✅ Фильтр по категориям (8 категорий)
|
||||
- ✅ Фильтр по статусу (все/прочитанные/непрочитанные)
|
||||
- ✅ Поиск по заголовку и тексту
|
||||
- ✅ Карточки уведомлений с действиями
|
||||
- ✅ Отметить прочитанным
|
||||
- ✅ Удаление уведомлений
|
||||
- ✅ Отметить все прочитанными
|
||||
- ✅ Адаптивный дизайн
|
||||
- ✅ Относительное время ("2 ч назад")
|
||||
|
||||
**Компоненты:**
|
||||
|
||||
**NotificationListManager класс:**
|
||||
```javascript
|
||||
class NotificationListManager {
|
||||
constructor() {
|
||||
this.currentPage = 1;
|
||||
this.pageSize = 20;
|
||||
this.currentFilter = {
|
||||
category: '',
|
||||
is_read: '',
|
||||
search: ''
|
||||
};
|
||||
}
|
||||
|
||||
// Методы:
|
||||
- loadNotifications()
|
||||
- renderNotifications(data)
|
||||
- renderNotificationCard(notification)
|
||||
- renderPagination(data)
|
||||
- markAsRead(id)
|
||||
- markAllAsRead()
|
||||
- deleteNotification(id)
|
||||
- formatTime(dateString)
|
||||
}
|
||||
```
|
||||
|
||||
**Карточка уведомления:**
|
||||
- Иконка по категории (цветная)
|
||||
- Заголовок и текст
|
||||
- Время ("только что", "2 ч назад", "3 дн назад")
|
||||
- Кнопки действий:
|
||||
- Перейти (если есть action_url)
|
||||
- Прочитано (для непрочитанных)
|
||||
- Удалить
|
||||
|
||||
**Пагинация:**
|
||||
- Кнопки "Предыдущая"/"Следующая"
|
||||
- Умная нумерация страниц
|
||||
- Многоточие между далекими страницами
|
||||
- Активная страница подсвечена
|
||||
- Прокрутка наверх при смене страницы
|
||||
|
||||
---
|
||||
|
||||
### 3. Функциональные настройки
|
||||
|
||||
**Файл:** `static/js/notifications/notification-settings.js` (280 строк)
|
||||
|
||||
**Функционал:**
|
||||
- ✅ Настройка по 8 категориям
|
||||
- ✅ Главный переключатель (вкл/выкл категорию)
|
||||
- ✅ Переключатели каналов:
|
||||
- Веб-уведомления (активно)
|
||||
- Email (отключено, "скоро")
|
||||
- Telegram (отключено, "скоро")
|
||||
- ✅ Звуковые уведомления (глобально)
|
||||
- ✅ Автосохранение при изменении
|
||||
- ✅ Toast-уведомления об успехе/ошибке
|
||||
|
||||
**NotificationSettingsManager класс:**
|
||||
```javascript
|
||||
class NotificationSettingsManager {
|
||||
// Методы:
|
||||
- loadSettings()
|
||||
- renderSettings()
|
||||
- attachEventHandlers()
|
||||
- updateSetting(category, field, value)
|
||||
- setupSoundToggle()
|
||||
- showToast(message, type)
|
||||
}
|
||||
```
|
||||
|
||||
**Категории:**
|
||||
1. Коммуникации (💬)
|
||||
2. Документы (📄)
|
||||
3. Заявления (📋)
|
||||
4. Календарь (📅)
|
||||
5. Отдел (👥)
|
||||
6. Профиль (👤)
|
||||
7. Новости (📰)
|
||||
8. Система (⚙️)
|
||||
|
||||
---
|
||||
|
||||
### 4. Новые API endpoints
|
||||
|
||||
**Файл:** `notifications/api_views.py`
|
||||
|
||||
**Новый endpoint:**
|
||||
```python
|
||||
@api_view(['PUT'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def update_category_settings(request):
|
||||
"""
|
||||
Обновить настройки для всех уведомлений категории
|
||||
|
||||
POST /api/notifications/settings/category/update/
|
||||
{
|
||||
"category": "communications",
|
||||
"is_enabled": true,
|
||||
"web_enabled": true,
|
||||
"email_enabled": false,
|
||||
"telegram_enabled": false
|
||||
}
|
||||
"""
|
||||
```
|
||||
|
||||
**Функционал:**
|
||||
- Получает код категории
|
||||
- Находит все типы уведомлений категории
|
||||
- Обновляет настройки для каждого типа
|
||||
- Возвращает количество обновленных записей
|
||||
|
||||
**URL:** `/api/notifications/settings/category/update/`
|
||||
|
||||
---
|
||||
|
||||
### 5. Обновленные шаблоны
|
||||
|
||||
**notification_list_new.html:**
|
||||
- Современный дизайн
|
||||
- Панель фильтров (3 колонки)
|
||||
- Строка поиска с кнопкой
|
||||
- Кнопка "Отметить все прочитанными"
|
||||
- Контейнер для динамического списка
|
||||
- Подключение notification-list.js
|
||||
|
||||
**notification_settings_new.html:**
|
||||
- Общие настройки (звук)
|
||||
- Настройки по категориям (8 карточек)
|
||||
- Информационная справка
|
||||
- Подключение notification-settings.js
|
||||
|
||||
---
|
||||
|
||||
## 📊 Статистика
|
||||
|
||||
### Код
|
||||
- **JavaScript файлов:** 3 (+2 новых)
|
||||
- **Строк JavaScript:** ~1300 (+750)
|
||||
- **HTML шаблонов:** 2 новых
|
||||
- **API endpoints:** +1
|
||||
- **CSS:** встроенные стили в шаблонах
|
||||
|
||||
### Функциональность
|
||||
- **Фильтров:** 3 (категория, статус, поиск)
|
||||
- **Действий с уведомлением:** 3 (прочитать, удалить, перейти)
|
||||
- **Настраиваемых категорий:** 8
|
||||
- **Каналов уведомлений:** 3 (веб активно, email/telegram скоро)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 UI Features
|
||||
|
||||
### Список уведомлений
|
||||
- ✅ Карточки с цветными иконками
|
||||
- ✅ Разная opacity для прочитанных (70%)
|
||||
- ✅ Синяя полоса слева для непрочитанных
|
||||
- ✅ Адаптивная сетка (1 колонка)
|
||||
- ✅ Умная пагинация
|
||||
- ✅ Пустое состояние (иконка + текст)
|
||||
- ✅ Состояние загрузки (спиннер)
|
||||
|
||||
### Настройки
|
||||
- ✅ Карточки категорий (2 колонки на больших экранах)
|
||||
- ✅ Градиентные иконки
|
||||
- ✅ Переключатели (switches)
|
||||
- ✅ Отключенное состояние для email/telegram
|
||||
- ✅ Информационная панель
|
||||
- ✅ Toast-уведомления
|
||||
|
||||
### Звук
|
||||
- ✅ Короткий приятный звук (300ms)
|
||||
- ✅ Частота 800 Hz (sine wave)
|
||||
- ✅ Плавное затухание
|
||||
- ✅ Не раздражающий
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Технические решения
|
||||
|
||||
### localStorage
|
||||
```javascript
|
||||
localStorage.setItem('notificationSoundEnabled', 'true');
|
||||
const enabled = localStorage.getItem('notificationSoundEnabled');
|
||||
```
|
||||
|
||||
### Web Audio API
|
||||
```javascript
|
||||
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
const oscillator = audioContext.createOscillator();
|
||||
const gainNode = audioContext.createGain();
|
||||
```
|
||||
|
||||
### Фильтрация API
|
||||
```javascript
|
||||
const params = new URLSearchParams({
|
||||
page: this.currentPage,
|
||||
page_size: this.pageSize,
|
||||
category: 'communications',
|
||||
is_read: 'false',
|
||||
search: 'документ'
|
||||
});
|
||||
```
|
||||
|
||||
### Относительное время
|
||||
```javascript
|
||||
formatTime(dateString) {
|
||||
const diff = Math.floor((now - date) / 1000);
|
||||
if (diff < 60) return 'только что';
|
||||
if (diff < 3600) return `${Math.floor(diff / 60)} мин назад`;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 Файлы проекта
|
||||
|
||||
```
|
||||
backend/
|
||||
├── static/js/notifications/
|
||||
│ ├── notification-manager.js ✅ Обновлен (звук)
|
||||
│ ├── notification-list.js ✅ НОВЫЙ (470 строк)
|
||||
│ └── notification-settings.js ✅ НОВЫЙ (280 строк)
|
||||
│
|
||||
├── templates/notifications/
|
||||
│ ├── notification_list_new.html ✅ НОВЫЙ
|
||||
│ └── notification_settings_new.html ✅ НОВЫЙ
|
||||
│
|
||||
├── notifications/
|
||||
│ ├── api_views.py ✅ Обновлен (+1 endpoint)
|
||||
│ └── api_urls.py ✅ Обновлен (+1 URL)
|
||||
│
|
||||
└── NOTIFICATION_STAGE4_COMPLETE.md ✅ Этот документ
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Как использовать
|
||||
|
||||
### 1. Список уведомлений
|
||||
|
||||
Открыть: `/notifications/`
|
||||
|
||||
**Фильтрация:**
|
||||
1. Выбрать категорию из выпадающего списка
|
||||
2. Выбрать статус (все/прочитанные/непрочитанные)
|
||||
3. Ввести текст для поиска и нажать "Найти"
|
||||
|
||||
**Действия:**
|
||||
- Клик на "Посмотреть" - переход к объекту
|
||||
- Клик на "Прочитано" - отметить уведомление
|
||||
- Клик на корзину - удалить уведомление
|
||||
- Клик на "Отметить все прочитанными" - массовое действие
|
||||
|
||||
**Навигация:**
|
||||
- Клик на номер страницы - перейти на страницу
|
||||
- Клик на стрелки - предыдущая/следующая страница
|
||||
|
||||
### 2. Настройки
|
||||
|
||||
Открыть: `/notifications/settings/`
|
||||
|
||||
**Звук:**
|
||||
1. Переключить "Звуковые уведомления" (глобально)
|
||||
2. Изменения сохраняются автоматически в localStorage
|
||||
|
||||
**Категории:**
|
||||
1. Переключить главный переключатель категории (вкл/выкл)
|
||||
2. При включении раскрываются каналы
|
||||
3. Переключить нужные каналы (веб/email/telegram)
|
||||
4. Изменения сохраняются автоматически через API
|
||||
|
||||
**Toast-уведомления:**
|
||||
- "Настройки сохранены" - успех (зеленый)
|
||||
- "Звук включен" / "Звук выключен" - инфо (синий)
|
||||
- "Ошибка сохранения" - ошибка (красный)
|
||||
|
||||
### 3. API
|
||||
|
||||
**Обновить настройки категории:**
|
||||
```javascript
|
||||
fetch('/api/notifications/settings/category/update/', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken
|
||||
},
|
||||
body: JSON.stringify({
|
||||
category: 'communications',
|
||||
is_enabled: true,
|
||||
web_enabled: true
|
||||
})
|
||||
});
|
||||
```
|
||||
|
||||
**Получить список с фильтрами:**
|
||||
```javascript
|
||||
fetch('/api/notifications/?page=1&category=documents&is_read=false&search=акт');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✨ Улучшения
|
||||
|
||||
### По сравнению с Этапом 3:
|
||||
|
||||
**Было:**
|
||||
- Простая страница списка без функционала
|
||||
- Страница настроек-заглушка
|
||||
- Только dropdown в navbar
|
||||
|
||||
**Стало:**
|
||||
- ✅ Полнофункциональный список с пагинацией
|
||||
- ✅ 3 фильтра + поиск
|
||||
- ✅ Действия с уведомлениями (прочитать/удалить)
|
||||
- ✅ Настройки по 8 категориям
|
||||
- ✅ Звуковые уведомления
|
||||
- ✅ Toast-сообщения
|
||||
- ✅ Автосохранение настроек
|
||||
- ✅ Относительное время
|
||||
- ✅ Современный UI
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Итоги
|
||||
|
||||
**Этап 4 ПОЛНОСТЬЮ ЗАВЕРШЕН!**
|
||||
|
||||
Создан современный пользовательский интерфейс с:
|
||||
- ✅ Удобной фильтрацией и поиском
|
||||
- ✅ Пагинацией для больших списков
|
||||
- ✅ Гибкими настройками по категориям
|
||||
- ✅ Звуковыми уведомлениями
|
||||
- ✅ Интуитивным управлением
|
||||
- ✅ Адаптивным дизайном
|
||||
|
||||
**Система готова к использованию!** 🚀
|
||||
|
||||
---
|
||||
|
||||
## 📈 Следующие этапы
|
||||
|
||||
### Этап 5: Celery и напоминания
|
||||
- Настройка Celery
|
||||
- Periodic tasks для напоминаний
|
||||
- Напоминания о событиях календаря
|
||||
- Напоминания о документах
|
||||
|
||||
### Этап 2 (отложенный): Email и Telegram
|
||||
- Email sender
|
||||
- Telegram bot
|
||||
- Шаблоны для каналов
|
||||
- Rate limiting
|
||||
|
||||
### Дополнительно:
|
||||
- Push-уведомления (PWA)
|
||||
- Группировка уведомлений
|
||||
- Дайджесты (ежедневные/еженедельные)
|
||||
- Экспорт уведомлений
|
||||
|
||||
---
|
||||
|
||||
*Дата завершения: 20 ноября 2025 г.*
|
||||
*Время разработки: 1 сессия*
|
||||
*Автор: GitHub Copilot + User*
|
||||
@ -1,978 +0,0 @@
|
||||
# План системы уведомлений EUSRR
|
||||
|
||||
## Дата: 20 ноября 2025
|
||||
## Статус: Проектирование
|
||||
|
||||
---
|
||||
|
||||
## 1. Обзор системы
|
||||
|
||||
### 1.1 Цели
|
||||
- ✅ Централизованная система уведомлений для всех событий в системе
|
||||
- ✅ Гибкие настройки для каждого пользователя
|
||||
- ✅ Множественные каналы доставки (сайт, email, Telegram, WhatsApp, WeChat)
|
||||
- ✅ Реальное время через WebSocket
|
||||
- ✅ История всех уведомлений
|
||||
- ✅ Группировка и приоритизация
|
||||
|
||||
### 1.2 Типы уведомлений
|
||||
|
||||
#### Коммуникации (Communications)
|
||||
- 💬 Новое сообщение в чате
|
||||
- 📌 Упоминание (@username) в сообщении
|
||||
- ✏️ Редактирование важного сообщения
|
||||
- 📎 Новое вложение в чате
|
||||
- 👥 Добавление в новый чат/группу
|
||||
- 🔔 Ответ на ваше сообщение
|
||||
|
||||
#### Документы (Documents)
|
||||
- 📄 Новый документ на ознакомление
|
||||
- ✅ Документ подписан всеми участниками
|
||||
- ⏰ Напоминание о необходимости ознакомления
|
||||
- 📝 Комментарий к документу
|
||||
- 🔄 Обновление документа
|
||||
|
||||
#### Заявления (Requests)
|
||||
- 📋 Новая заявка (для ответственных)
|
||||
- ✅ Заявка одобрена
|
||||
- ❌ Заявка отклонена
|
||||
- 💬 Комментарий к вашей заявке
|
||||
- 🔄 Изменение статуса заявки
|
||||
- ⏰ Заявка на рассмотрении долгое время
|
||||
|
||||
#### Календарь (Calendar)
|
||||
- 📅 Новое событие
|
||||
- ⏰ Напоминание о событии (за час, за день)
|
||||
- 🔄 Изменение события
|
||||
- ❌ Отмена события
|
||||
- 👥 Приглашение на событие
|
||||
|
||||
#### Отдел (Department)
|
||||
- 👥 Новый сотрудник в отделе
|
||||
- 👋 Сотрудник покинул отдел
|
||||
- 📊 Изменение структуры отдела
|
||||
- 👤 Назначен новый руководитель
|
||||
- 📢 Объявление для отдела
|
||||
|
||||
#### Профиль (Profile)
|
||||
- ✏️ Изменение данных профиля администратором
|
||||
- 🔑 Изменение пароля
|
||||
- 📧 Изменение email
|
||||
- 📱 Привязка нового мессенджера
|
||||
- 🔐 Вход из нового места/устройства
|
||||
|
||||
#### Новости (Feed)
|
||||
- 📰 Новая новость
|
||||
- 💬 Комментарий к вашей новости
|
||||
- ❤️ Реакция на вашу новость
|
||||
|
||||
#### Система (System)
|
||||
- ⚠️ Технические работы
|
||||
- 📢 Важное объявление
|
||||
- 🎉 Новый функционал
|
||||
- 🔒 Изменение политики безопасности
|
||||
|
||||
---
|
||||
|
||||
## 2. Архитектура базы данных
|
||||
|
||||
### 2.1 Модели
|
||||
|
||||
#### `NotificationCategory` (Категория уведомлений)
|
||||
```python
|
||||
class NotificationCategory(models.Model):
|
||||
"""Категории уведомлений для группировки"""
|
||||
|
||||
CATEGORY_CHOICES = [
|
||||
('communications', 'Коммуникации'),
|
||||
('documents', 'Документы'),
|
||||
('requests', 'Заявления'),
|
||||
('calendar', 'Календарь'),
|
||||
('department', 'Отдел'),
|
||||
('profile', 'Профиль'),
|
||||
('feed', 'Новости'),
|
||||
('system', 'Система'),
|
||||
]
|
||||
|
||||
code = models.CharField(max_length=50, unique=True, choices=CATEGORY_CHOICES)
|
||||
name = models.CharField(max_length=100)
|
||||
description = models.TextField(blank=True)
|
||||
icon = models.CharField(max_length=50, default='bi-bell') # Bootstrap icon
|
||||
color = models.CharField(max_length=20, default='primary') # Bootstrap color
|
||||
is_active = models.BooleanField(default=True)
|
||||
order = models.IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ['order', 'name']
|
||||
verbose_name = 'Категория уведомлений'
|
||||
verbose_name_plural = 'Категории уведомлений'
|
||||
```
|
||||
|
||||
#### `NotificationType` (Тип уведомления)
|
||||
```python
|
||||
class NotificationType(models.Model):
|
||||
"""Конкретные типы уведомлений внутри категорий"""
|
||||
|
||||
category = models.ForeignKey(NotificationCategory, on_delete=models.CASCADE, related_name='types')
|
||||
code = models.CharField(max_length=100, unique=True) # 'chat_new_message', 'document_ready', etc.
|
||||
name = models.CharField(max_length=200)
|
||||
description = models.TextField(blank=True)
|
||||
|
||||
# Настройки по умолчанию для всех пользователей
|
||||
default_enabled = models.BooleanField(default=True)
|
||||
default_channels = models.JSONField(default=dict) # {'web': True, 'email': False, 'telegram': False}
|
||||
|
||||
# Приоритет
|
||||
PRIORITY_CHOICES = [
|
||||
('low', 'Низкий'),
|
||||
('normal', 'Обычный'),
|
||||
('high', 'Высокий'),
|
||||
('urgent', 'Срочный'),
|
||||
]
|
||||
priority = models.CharField(max_length=20, choices=PRIORITY_CHOICES, default='normal')
|
||||
|
||||
# Можно ли отключить
|
||||
is_required = models.BooleanField(default=False) # Например, системные уведомления нельзя отключить
|
||||
|
||||
# Группировка (например, объединять несколько сообщений в одно уведомление)
|
||||
is_groupable = models.BooleanField(default=True)
|
||||
grouping_window_minutes = models.IntegerField(default=5) # Группировать за последние 5 минут
|
||||
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['category__order', 'name']
|
||||
verbose_name = 'Тип уведомления'
|
||||
verbose_name_plural = 'Типы уведомлений'
|
||||
```
|
||||
|
||||
#### `Notification` (Уведомление)
|
||||
```python
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
class Notification(models.Model):
|
||||
"""Конкретное уведомление для пользователя"""
|
||||
|
||||
recipient = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='notifications'
|
||||
)
|
||||
notification_type = models.ForeignKey(NotificationType, on_delete=models.CASCADE)
|
||||
|
||||
# Заголовок и текст
|
||||
title = models.CharField(max_length=255)
|
||||
message = models.TextField()
|
||||
short_message = models.CharField(max_length=150, blank=True) # Для preview
|
||||
|
||||
# Связь с объектом (GenericForeignKey)
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, null=True, blank=True)
|
||||
object_id = models.PositiveIntegerField(null=True, blank=True)
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
|
||||
# Метаданные
|
||||
metadata = models.JSONField(default=dict, blank=True) # Доп. данные для рендеринга
|
||||
|
||||
# Ссылка действия
|
||||
action_url = models.CharField(max_length=500, blank=True)
|
||||
action_text = models.CharField(max_length=100, default='Посмотреть')
|
||||
|
||||
# Статусы
|
||||
is_read = models.BooleanField(default=False)
|
||||
read_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
is_archived = models.BooleanField(default=False)
|
||||
archived_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
# Доставка
|
||||
sent_web = models.BooleanField(default=False)
|
||||
sent_email = models.BooleanField(default=False)
|
||||
sent_telegram = models.BooleanField(default=False)
|
||||
sent_whatsapp = models.BooleanField(default=False)
|
||||
sent_wechat = models.BooleanField(default=False)
|
||||
|
||||
sent_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
# Группировка
|
||||
group_key = models.CharField(max_length=255, blank=True, db_index=True) # Ключ для группировки
|
||||
is_grouped = models.BooleanField(default=False)
|
||||
grouped_count = models.IntegerField(default=1) # Количество объединенных уведомлений
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['recipient', '-created_at']),
|
||||
models.Index(fields=['recipient', 'is_read']),
|
||||
models.Index(fields=['notification_type', '-created_at']),
|
||||
models.Index(fields=['group_key', '-created_at']),
|
||||
]
|
||||
verbose_name = 'Уведомление'
|
||||
verbose_name_plural = 'Уведомления'
|
||||
|
||||
def mark_as_read(self):
|
||||
if not self.is_read:
|
||||
self.is_read = True
|
||||
self.read_at = timezone.now()
|
||||
self.save(update_fields=['is_read', 'read_at'])
|
||||
```
|
||||
|
||||
#### `UserNotificationSettings` (Настройки пользователя)
|
||||
```python
|
||||
class UserNotificationSettings(models.Model):
|
||||
"""Персональные настройки уведомлений пользователя"""
|
||||
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='notification_settings')
|
||||
notification_type = models.ForeignKey(NotificationType, on_delete=models.CASCADE)
|
||||
|
||||
# Включено ли уведомление
|
||||
is_enabled = models.BooleanField(default=True)
|
||||
|
||||
# Каналы доставки
|
||||
send_web = models.BooleanField(default=True) # На сайте
|
||||
send_email = models.BooleanField(default=False)
|
||||
send_telegram = models.BooleanField(default=False)
|
||||
send_whatsapp = models.BooleanField(default=False)
|
||||
send_wechat = models.BooleanField(default=False)
|
||||
|
||||
# Тихий режим (не беспокоить)
|
||||
quiet_hours_enabled = models.BooleanField(default=False)
|
||||
quiet_start_time = models.TimeField(null=True, blank=True) # 22:00
|
||||
quiet_end_time = models.TimeField(null=True, blank=True) # 08:00
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ['user', 'notification_type']
|
||||
verbose_name = 'Настройка уведомлений пользователя'
|
||||
verbose_name_plural = 'Настройки уведомлений пользователей'
|
||||
```
|
||||
|
||||
#### `NotificationTemplate` (Шаблоны)
|
||||
```python
|
||||
class NotificationTemplate(models.Model):
|
||||
"""Шаблоны для разных каналов доставки"""
|
||||
|
||||
notification_type = models.ForeignKey(NotificationType, on_delete=models.CASCADE, related_name='templates')
|
||||
|
||||
CHANNEL_CHOICES = [
|
||||
('web', 'Веб'),
|
||||
('email', 'Email'),
|
||||
('telegram', 'Telegram'),
|
||||
('whatsapp', 'WhatsApp'),
|
||||
('wechat', 'WeChat'),
|
||||
]
|
||||
channel = models.CharField(max_length=20, choices=CHANNEL_CHOICES)
|
||||
|
||||
# Шаблоны (используем Django template syntax)
|
||||
title_template = models.TextField()
|
||||
message_template = models.TextField()
|
||||
|
||||
# Для email
|
||||
html_template = models.TextField(blank=True)
|
||||
|
||||
# Для кнопок действий
|
||||
action_button_template = models.CharField(max_length=200, blank=True)
|
||||
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ['notification_type', 'channel']
|
||||
verbose_name = 'Шаблон уведомления'
|
||||
verbose_name_plural = 'Шаблоны уведомлений'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Этапы реализации
|
||||
|
||||
### Этап 1: Базовая инфраструктура (Week 1-2)
|
||||
**Цель:** Создать основу системы уведомлений
|
||||
|
||||
#### Задачи:
|
||||
1. ✅ Создать приложение `notifications`
|
||||
2. ✅ Создать модели:
|
||||
- `NotificationCategory`
|
||||
- `NotificationType`
|
||||
- `Notification`
|
||||
- `UserNotificationSettings`
|
||||
- `NotificationTemplate`
|
||||
3. ✅ Создать миграции
|
||||
4. ✅ Настроить Django Admin для всех моделей
|
||||
5. ✅ Создать начальные данные (fixtures) с типами уведомлений
|
||||
|
||||
**Файлы:**
|
||||
```
|
||||
notifications/
|
||||
├── __init__.py
|
||||
├── models.py
|
||||
├── admin.py
|
||||
├── apps.py
|
||||
├── migrations/
|
||||
├── fixtures/
|
||||
│ └── initial_notification_types.json
|
||||
└── management/
|
||||
└── commands/
|
||||
└── create_notification_types.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Этап 2: Сервисный слой (Week 2-3)
|
||||
**Цель:** Создать API для создания и управления уведомлениями
|
||||
|
||||
#### Задачи:
|
||||
1. ✅ Создать `NotificationService` для создания уведомлений
|
||||
2. ✅ Создать `NotificationSender` для отправки по каналам
|
||||
3. ✅ Создать систему шаблонов
|
||||
4. ✅ Реализовать группировку уведомлений
|
||||
5. ✅ Создать Celery tasks для асинхронной отправки
|
||||
|
||||
**Файлы:**
|
||||
```python
|
||||
# notifications/services.py
|
||||
class NotificationService:
|
||||
@staticmethod
|
||||
def create_notification(
|
||||
recipient,
|
||||
notification_type_code,
|
||||
title,
|
||||
message,
|
||||
content_object=None,
|
||||
action_url='',
|
||||
metadata=None
|
||||
):
|
||||
"""Создать и отправить уведомление"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def send_notification(notification):
|
||||
"""Отправить уведомление по всем каналам"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def mark_as_read(notification_id, user):
|
||||
"""Отметить как прочитанное"""
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def mark_all_as_read(user, category=None):
|
||||
"""Отметить все как прочитанные"""
|
||||
pass
|
||||
|
||||
# notifications/senders.py
|
||||
class WebNotificationSender:
|
||||
def send(self, notification):
|
||||
"""Отправка через WebSocket"""
|
||||
pass
|
||||
|
||||
class EmailNotificationSender:
|
||||
def send(self, notification):
|
||||
"""Отправка через email"""
|
||||
pass
|
||||
|
||||
class TelegramNotificationSender:
|
||||
def send(self, notification):
|
||||
"""Отправка через Telegram бота"""
|
||||
pass
|
||||
|
||||
# notifications/tasks.py
|
||||
from celery import shared_task
|
||||
|
||||
@shared_task
|
||||
def send_notification_async(notification_id):
|
||||
"""Асинхронная отправка уведомления"""
|
||||
pass
|
||||
|
||||
@shared_task
|
||||
def cleanup_old_notifications():
|
||||
"""Очистка старых уведомлений (старше 90 дней)"""
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Этап 3: WebSocket реального времени (Week 3)
|
||||
**Цель:** Уведомления в реальном времени на сайте
|
||||
|
||||
#### Задачи:
|
||||
1. ✅ Создать WebSocket consumer для уведомлений
|
||||
2. ✅ Подключить к routing
|
||||
3. ✅ Создать фронтенд компонент для колокольчика
|
||||
4. ✅ Создать всплывающие toast-уведомления
|
||||
5. ✅ Звуковые уведомления (опционально)
|
||||
|
||||
**Файлы:**
|
||||
```python
|
||||
# notifications/consumers.py
|
||||
class NotificationConsumer(AsyncWebsocketConsumer):
|
||||
async def connect(self):
|
||||
self.user = self.scope['user']
|
||||
await self.channel_layer.group_add(
|
||||
f'notifications_{self.user.id}',
|
||||
self.channel_name
|
||||
)
|
||||
await self.accept()
|
||||
|
||||
async def notification_new(self, event):
|
||||
"""Отправить новое уведомление"""
|
||||
await self.send(text_data=json.dumps({
|
||||
'type': 'new_notification',
|
||||
'notification': event['notification']
|
||||
}))
|
||||
|
||||
# notifications/routing.py
|
||||
from django.urls import re_path
|
||||
from . import consumers
|
||||
|
||||
websocket_urlpatterns = [
|
||||
re_path(r'^ws/notifications/$', consumers.NotificationConsumer.as_asgi()),
|
||||
]
|
||||
```
|
||||
|
||||
**Frontend:**
|
||||
```javascript
|
||||
// static/js/notifications.js
|
||||
class NotificationManager {
|
||||
constructor() {
|
||||
this.ws = null;
|
||||
this.unreadCount = 0;
|
||||
this.connectWebSocket();
|
||||
}
|
||||
|
||||
connectWebSocket() {
|
||||
const wsUrl = `ws://${window.location.host}/ws/notifications/`;
|
||||
this.ws = new WebSocket(wsUrl);
|
||||
|
||||
this.ws.onmessage = (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.type === 'new_notification') {
|
||||
this.handleNewNotification(data.notification);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleNewNotification(notification) {
|
||||
// Обновить счетчик
|
||||
this.updateBadge();
|
||||
|
||||
// Показать toast
|
||||
this.showToast(notification);
|
||||
|
||||
// Воспроизвести звук
|
||||
this.playSound();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Этап 4: UI компоненты (Week 4)
|
||||
**Цель:** Интерфейс для просмотра и управления уведомлениями
|
||||
|
||||
#### Задачи:
|
||||
1. ✅ Обновить navbar с счетчиком уведомлений
|
||||
2. ✅ Создать dropdown список уведомлений
|
||||
3. ✅ Создать страницу всех уведомлений
|
||||
4. ✅ Создать страницу настроек уведомлений
|
||||
5. ✅ Фильтрация по категориям
|
||||
6. ✅ Поиск в уведомлениях
|
||||
|
||||
**Компоненты:**
|
||||
```
|
||||
templates/notifications/
|
||||
├── notification_dropdown.html # Dropdown в navbar
|
||||
├── notification_list.html # Полный список
|
||||
├── notification_settings.html # Настройки
|
||||
└── includes/
|
||||
├── notification_item.html # Карточка уведомления
|
||||
└── notification_toast.html # Toast-уведомление
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Этап 5: Интеграция с модулями (Week 5-6)
|
||||
**Цель:** Добавить уведомления во все существующие модули
|
||||
|
||||
#### 5.1 Communications (Чаты)
|
||||
```python
|
||||
# communications/signals.py
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from notifications.services import NotificationService
|
||||
|
||||
@receiver(post_save, sender=Message)
|
||||
def notify_new_message(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
# Получить участников чата, кроме автора
|
||||
recipients = instance.chat.participants.exclude(id=instance.author.id)
|
||||
|
||||
for recipient in recipients:
|
||||
# Проверить, упомянут ли пользователь
|
||||
is_mentioned = f'@{recipient.username}' in instance.content
|
||||
|
||||
notification_type = 'chat_mention' if is_mentioned else 'chat_new_message'
|
||||
|
||||
NotificationService.create_notification(
|
||||
recipient=recipient,
|
||||
notification_type_code=notification_type,
|
||||
title=f'Новое сообщение от {instance.author.get_full_name()}',
|
||||
message=instance.content[:100],
|
||||
content_object=instance,
|
||||
action_url=f'/communications/chat/{instance.chat.id}/',
|
||||
metadata={
|
||||
'chat_id': instance.chat.id,
|
||||
'chat_name': str(instance.chat),
|
||||
'author_name': instance.author.get_full_name(),
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### 5.2 Documents
|
||||
```python
|
||||
@receiver(post_save, sender=DocumentAcknowledgement)
|
||||
def notify_document_ready(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
NotificationService.create_notification(
|
||||
recipient=instance.user,
|
||||
notification_type_code='document_ready',
|
||||
title='Новый документ на ознакомление',
|
||||
message=f'Документ "{instance.document.title}" требует вашего ознакомления',
|
||||
content_object=instance.document,
|
||||
action_url=f'/documents/{instance.document.id}/',
|
||||
)
|
||||
```
|
||||
|
||||
#### 5.3 Requests (Заявления)
|
||||
```python
|
||||
@receiver(post_save, sender=Request)
|
||||
def notify_request_status_change(sender, instance, created, **kwargs):
|
||||
if not created and instance.tracker.has_changed('status'):
|
||||
NotificationService.create_notification(
|
||||
recipient=instance.created_by,
|
||||
notification_type_code='request_status_changed',
|
||||
title=f'Статус заявки изменен',
|
||||
message=f'Ваша заявка "{instance.title}" теперь: {instance.get_status_display()}',
|
||||
content_object=instance,
|
||||
action_url=f'/requests/{instance.id}/',
|
||||
)
|
||||
```
|
||||
|
||||
#### 5.4 Calendar
|
||||
```python
|
||||
@receiver(post_save, sender=Event)
|
||||
def notify_event_created(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
for participant in instance.participants.all():
|
||||
NotificationService.create_notification(
|
||||
recipient=participant,
|
||||
notification_type_code='event_created',
|
||||
title='Новое событие в календаре',
|
||||
message=f'{instance.title} - {instance.start_date.strftime("%d.%m.%Y %H:%M")}',
|
||||
content_object=instance,
|
||||
action_url='/calendar/',
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Этап 6: API для настроек (Week 6)
|
||||
**Цель:** REST API для управления настройками
|
||||
|
||||
#### Задачи:
|
||||
```python
|
||||
# notifications/api_views.py
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_notifications(request):
|
||||
"""Получить список уведомлений с пагинацией"""
|
||||
pass
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_unread_count(request):
|
||||
"""Получить количество непрочитанных"""
|
||||
pass
|
||||
|
||||
@api_view(['POST'])
|
||||
def mark_as_read(request, notification_id):
|
||||
"""Отметить как прочитанное"""
|
||||
pass
|
||||
|
||||
@api_view(['POST'])
|
||||
def mark_all_as_read(request):
|
||||
"""Отметить все как прочитанные"""
|
||||
pass
|
||||
|
||||
@api_view(['GET'])
|
||||
def get_settings(request):
|
||||
"""Получить настройки пользователя"""
|
||||
pass
|
||||
|
||||
@api_view(['POST'])
|
||||
def update_settings(request):
|
||||
"""Обновить настройки"""
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Этап 7: Email рассылка (Week 7)
|
||||
**Цель:** Отправка уведомлений на email
|
||||
|
||||
#### Задачи:
|
||||
1. ✅ Создать HTML-шаблоны для email
|
||||
2. ✅ Настроить Celery задачи для пакетной отправки
|
||||
3. ✅ Дайджест уведомлений (раз в день/неделю)
|
||||
4. ✅ Настройки частоты email
|
||||
|
||||
**Шаблоны:**
|
||||
```django
|
||||
{# notifications/email/base.html #}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
/* Email styles */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<img src="{{ BRAND_LOGO }}" alt="{{ BRAND_NAME }}">
|
||||
</div>
|
||||
{% block content %}{% endblock %}
|
||||
<div class="footer">
|
||||
<a href="{{ settings_url }}">Настроить уведомления</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Этап 8: Telegram бот (Week 8-9)
|
||||
**Цель:** Отправка уведомлений через Telegram
|
||||
|
||||
#### Задачи:
|
||||
1. ✅ Создать Telegram бота (python-telegram-bot)
|
||||
2. ✅ Реализовать привязку аккаунта
|
||||
3. ✅ Отправка уведомлений
|
||||
4. ✅ Интерактивные кнопки в Telegram
|
||||
5. ✅ Настройки через бота
|
||||
|
||||
**Структура:**
|
||||
```python
|
||||
# notifications/telegram_bot.py
|
||||
from telegram import Update, InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from telegram.ext import ApplicationBuilder, CommandHandler, CallbackQueryHandler
|
||||
|
||||
class NotificationBot:
|
||||
def __init__(self, token):
|
||||
self.app = ApplicationBuilder().token(token).build()
|
||||
self.register_handlers()
|
||||
|
||||
def register_handlers(self):
|
||||
self.app.add_handler(CommandHandler('start', self.start))
|
||||
self.app.add_handler(CommandHandler('settings', self.settings))
|
||||
self.app.add_handler(CallbackQueryHandler(self.button_callback))
|
||||
|
||||
async def start(self, update: Update, context):
|
||||
"""Команда /start для привязки"""
|
||||
pass
|
||||
|
||||
async def settings(self, update: Update, context):
|
||||
"""Настройки уведомлений"""
|
||||
pass
|
||||
|
||||
def send_notification(self, telegram_id, notification):
|
||||
"""Отправить уведомление"""
|
||||
pass
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Этап 9: Оптимизация и производительность (Week 10)
|
||||
**Цель:** Оптимизировать систему для большой нагрузки
|
||||
|
||||
#### Задачи:
|
||||
1. ✅ Кеширование счетчиков (Redis)
|
||||
2. ✅ Пакетная отправка
|
||||
3. ✅ Индексы БД
|
||||
4. ✅ Архивирование старых уведомлений
|
||||
5. ✅ Rate limiting для защиты от спама
|
||||
6. ✅ Мониторинг производительности
|
||||
|
||||
---
|
||||
|
||||
## 4. Технологический стек
|
||||
|
||||
### Backend
|
||||
- **Django**: Основной фреймворк
|
||||
- **Django Channels**: WebSocket для real-time
|
||||
- **Celery**: Асинхронные задачи
|
||||
- **Redis**: Кеширование, message broker для Celery
|
||||
- **PostgreSQL**: Основная БД
|
||||
|
||||
### Frontend
|
||||
- **Bootstrap 5**: UI компоненты
|
||||
- **Bootstrap Icons**: Иконки
|
||||
- **Vanilla JavaScript**: Реальное время
|
||||
- **Service Workers**: Push уведомления (будущее)
|
||||
|
||||
### Внешние сервисы
|
||||
- **SMTP**: Email рассылка
|
||||
- **Telegram Bot API**: Telegram уведомления
|
||||
- **WhatsApp Business API**: WhatsApp (будущее)
|
||||
- **WeChat API**: WeChat (будущее)
|
||||
|
||||
---
|
||||
|
||||
## 5. Структура проекта
|
||||
|
||||
```
|
||||
backend/
|
||||
├── notifications/
|
||||
│ ├── __init__.py
|
||||
│ ├── models.py # Модели
|
||||
│ ├── admin.py # Django Admin
|
||||
│ ├── apps.py
|
||||
│ ├── services.py # Бизнес-логика
|
||||
│ ├── senders.py # Отправка по каналам
|
||||
│ ├── tasks.py # Celery задачи
|
||||
│ ├── consumers.py # WebSocket consumers
|
||||
│ ├── routing.py # WebSocket routing
|
||||
│ ├── telegram_bot.py # Telegram бот
|
||||
│ ├── api_views.py # REST API
|
||||
│ ├── urls.py # URL patterns
|
||||
│ ├── views.py # Django views
|
||||
│ ├── serializers.py # DRF serializers
|
||||
│ ├── signals.py # Django signals
|
||||
│ ├── fixtures/
|
||||
│ │ └── initial_data.json
|
||||
│ ├── management/
|
||||
│ │ └── commands/
|
||||
│ │ ├── create_notification_types.py
|
||||
│ │ └── run_telegram_bot.py
|
||||
│ ├── migrations/
|
||||
│ └── tests/
|
||||
│ ├── test_models.py
|
||||
│ ├── test_services.py
|
||||
│ └── test_api.py
|
||||
│
|
||||
├── templates/
|
||||
│ └── notifications/
|
||||
│ ├── notification_list.html
|
||||
│ ├── notification_settings.html
|
||||
│ ├── notification_dropdown.html
|
||||
│ ├── email/
|
||||
│ │ ├── base.html
|
||||
│ │ ├── new_message.html
|
||||
│ │ └── digest.html
|
||||
│ └── includes/
|
||||
│ ├── notification_item.html
|
||||
│ └── notification_toast.html
|
||||
│
|
||||
└── static/
|
||||
├── js/
|
||||
│ ├── notifications.js # Главный класс
|
||||
│ └── notification-toast.js # Toast компонент
|
||||
├── css/
|
||||
│ └── notifications.css
|
||||
└── sounds/
|
||||
└── notification.mp3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. API Endpoints
|
||||
|
||||
### REST API
|
||||
```
|
||||
GET /api/notifications/ # Список уведомлений
|
||||
GET /api/notifications/unread/ # Только непрочитанные
|
||||
GET /api/notifications/count/ # Счетчик
|
||||
POST /api/notifications/{id}/read/ # Отметить прочитанным
|
||||
POST /api/notifications/read-all/ # Все прочитанными
|
||||
DELETE /api/notifications/{id}/ # Удалить
|
||||
POST /api/notifications/archive-all/ # Архивировать все
|
||||
|
||||
GET /api/notifications/settings/ # Настройки пользователя
|
||||
PUT /api/notifications/settings/ # Обновить настройки
|
||||
GET /api/notifications/types/ # Доступные типы
|
||||
```
|
||||
|
||||
### WebSocket
|
||||
```
|
||||
ws://domain/ws/notifications/ # Real-time уведомления
|
||||
|
||||
Сообщения:
|
||||
{
|
||||
"type": "new_notification",
|
||||
"notification": {...}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "mark_read",
|
||||
"notification_id": 123
|
||||
}
|
||||
|
||||
{
|
||||
"type": "count_update",
|
||||
"count": 5
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Примеры использования
|
||||
|
||||
### 7.1 Создание уведомления в коде
|
||||
```python
|
||||
from notifications.services import NotificationService
|
||||
|
||||
# Простое уведомление
|
||||
NotificationService.create_notification(
|
||||
recipient=user,
|
||||
notification_type_code='chat_new_message',
|
||||
title='Новое сообщение',
|
||||
message='У вас новое сообщение в чате',
|
||||
action_url='/communications/chat/123/',
|
||||
)
|
||||
|
||||
# С привязкой к объекту
|
||||
NotificationService.create_notification(
|
||||
recipient=user,
|
||||
notification_type_code='document_ready',
|
||||
title='Документ на ознакомление',
|
||||
message=document.title,
|
||||
content_object=document,
|
||||
action_url=f'/documents/{document.id}/',
|
||||
metadata={
|
||||
'deadline': document.deadline.isoformat(),
|
||||
'priority': 'high'
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### 7.2 Использование в signals
|
||||
```python
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from notifications.services import NotificationService
|
||||
|
||||
@receiver(post_save, sender=Message)
|
||||
def notify_chat_participants(sender, instance, created, **kwargs):
|
||||
if created:
|
||||
recipients = instance.chat.participants.exclude(id=instance.author.id)
|
||||
|
||||
for recipient in recipients:
|
||||
NotificationService.create_notification(
|
||||
recipient=recipient,
|
||||
notification_type_code='chat_new_message',
|
||||
title=f'{instance.author.get_full_name()}',
|
||||
message=instance.content,
|
||||
content_object=instance,
|
||||
action_url=f'/communications/chat/{instance.chat.id}/',
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Приоритеты и сроки
|
||||
|
||||
### MVP (Минимально работающий продукт) - 4 недели
|
||||
- ✅ Базовые модели
|
||||
- ✅ Уведомления на сайте (WebSocket)
|
||||
- ✅ Интеграция с чатами
|
||||
- ✅ Базовые настройки
|
||||
|
||||
### V1.0 - 6 недель
|
||||
- ✅ Email уведомления
|
||||
- ✅ Интеграция со всеми модулями
|
||||
- ✅ Полные настройки
|
||||
- ✅ UI компоненты
|
||||
|
||||
### V2.0 - 10 недель
|
||||
- ✅ Telegram бот
|
||||
- ✅ Группировка уведомлений
|
||||
- ✅ Дайджесты
|
||||
- ✅ Оптимизация
|
||||
|
||||
### V3.0 - Будущее
|
||||
- ⏳ WhatsApp интеграция
|
||||
- ⏳ WeChat интеграция
|
||||
- ⏳ Push уведомления (PWA)
|
||||
- ⏳ Мобильное приложение
|
||||
|
||||
---
|
||||
|
||||
## 9. Безопасность и приватность
|
||||
|
||||
### 9.1 Защита данных
|
||||
- Уведомления видны только получателю
|
||||
- Шифрование sensitive данных
|
||||
- Регулярная очистка старых уведомлений
|
||||
- GDPR compliance
|
||||
|
||||
### 9.2 Rate Limiting
|
||||
```python
|
||||
from django_ratelimit.decorators import ratelimit
|
||||
|
||||
@ratelimit(key='user', rate='100/h')
|
||||
def create_notification_view(request):
|
||||
"""Ограничение: 100 уведомлений в час на пользователя"""
|
||||
pass
|
||||
```
|
||||
|
||||
### 9.3 Permissions
|
||||
```python
|
||||
class NotificationPermission:
|
||||
@staticmethod
|
||||
def can_send_to(sender, recipient, notification_type):
|
||||
"""Проверка прав на отправку уведомления"""
|
||||
# Например, обычный пользователь не может слать system уведомления
|
||||
if notification_type.category.code == 'system':
|
||||
return sender.is_staff
|
||||
return True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Мониторинг и аналитика
|
||||
|
||||
### Метрики
|
||||
- Количество отправленных уведомлений (по типам)
|
||||
- Процент прочитанных уведомлений
|
||||
- Среднее время до прочтения
|
||||
- Популярные типы уведомлений
|
||||
- Предпочитаемые каналы пользователей
|
||||
|
||||
### Логирование
|
||||
```python
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('notifications')
|
||||
|
||||
logger.info(f'Notification sent: {notification.id} to {recipient.id}')
|
||||
logger.warning(f'Failed to send to Telegram: {telegram_id}')
|
||||
logger.error(f'Email delivery failed: {error}')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Заключение
|
||||
|
||||
Система уведомлений - критически важный компонент для engagement пользователей. План рассчитан на поэтапную реализацию с возможностью использования базового функционала уже через 4 недели.
|
||||
|
||||
**Следующий шаг:** Начать с Этапа 1 - создание моделей и базовой инфраструктуры.
|
||||
@ -1,325 +0,0 @@
|
||||
# Стандартизация шапок страниц - Краткая сводка
|
||||
|
||||
## 📦 Созданные компоненты
|
||||
|
||||
### 1. HTML компонент
|
||||
**Файл:** `templates/includes/components/page_header.html`
|
||||
|
||||
Универсальный компонент шапки с параметрами:
|
||||
- `icon` - иконка Bootstrap Icons
|
||||
- `title` - заголовок страницы
|
||||
- `count` - счётчик элементов
|
||||
- `search_id` - ID поля поиска
|
||||
- `has_filters` - наличие фильтров
|
||||
- `create_url` / `create_modal` - кнопка создания
|
||||
- `scope` / `scope_urls` - переключатель Мои/Все
|
||||
- И др. (см. полную документацию в файле)
|
||||
|
||||
### 2. CSS стили
|
||||
**Файл:** `static/css/components/page-header.css`
|
||||
|
||||
Особенности:
|
||||
- ✅ Полупрозрачный фон с backdrop-filter (iOS-стиль)
|
||||
- ✅ Sticky позиционирование (прилипает при скролле)
|
||||
- ✅ Адаптивная верстка (мобильные + планшеты + десктоп)
|
||||
- ✅ Вытянутая кнопка создания на десктопе, круглая на мобильных
|
||||
- ✅ Плавные анимации и transitions
|
||||
- ✅ Поддержка темной темы
|
||||
|
||||
### 3. Документация
|
||||
**Файл:** `PLAN_PAGE_HEADER_STANDARDIZATION.md`
|
||||
|
||||
Содержит:
|
||||
- Детальный анализ всех текущих шапок (7 страниц)
|
||||
- План внедрения по фазам (3 фазы)
|
||||
- Примеры использования
|
||||
- Чеклист для каждой страницы
|
||||
- Временные оценки (~6-7 часов)
|
||||
|
||||
### 4. Пример использования
|
||||
**Файл:** `templates/employees/employees_list_NEW_EXAMPLE.html`
|
||||
|
||||
Демонстрирует:
|
||||
- Как подключить компонент
|
||||
- Как добавить фильтры (collapse блок)
|
||||
- Как настроить параметры
|
||||
|
||||
---
|
||||
|
||||
## 📊 Анализ текущего состояния
|
||||
|
||||
### Проанализированные страницы:
|
||||
|
||||
| Страница | Статус | Приоритет | Время |
|
||||
|----------|--------|-----------|-------|
|
||||
| **employees_list.html** | Нужна модернизация | 🔴 Высокий | 30 мин |
|
||||
| **department_list.html** | Нужна модернизация | 🔴 Высокий | 30 мин |
|
||||
| **document_list.html** | Почти готово | 🟡 Средний | 1 час |
|
||||
| **request_list_full.html** | Нужна модернизация | 🟡 Средний | 1 час |
|
||||
| **chat_list.html** | Нужна модернизация | 🟡 Средний | 1 час |
|
||||
| **feed_list.html** | Специфичная | 🟢 Низкий | 30 мин |
|
||||
| **search/results.html** | Специфичная | 🟢 Низкий | - |
|
||||
|
||||
**Итого:** 7 страниц, ~6-7 часов работы
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Ключевые улучшения
|
||||
|
||||
### 1. Визуальные
|
||||
- Полупрозрачная шапка с размытием (как в iOS/macOS)
|
||||
- Единый стиль для всех страниц
|
||||
- Современные rounded кнопки
|
||||
- Плавные анимации
|
||||
|
||||
### 2. Функциональные
|
||||
- Sticky позиционирование (шапка всегда видна при скролле)
|
||||
- Адаптивная кнопка создания (текст на десктопе, иконка на мобильных)
|
||||
- Скрываемые фильтры (освобождают место)
|
||||
- Интегрированный поиск iOS-стиль
|
||||
|
||||
### 3. UX
|
||||
- Меньше визуального шума
|
||||
- Важные действия всегда под рукой
|
||||
- Быстрый доступ к фильтрам
|
||||
- Счётчик элементов виден сразу
|
||||
|
||||
### 4. Технические
|
||||
- Переиспользуемый компонент (DRY принцип)
|
||||
- Меньше дублирования CSS
|
||||
- Простота поддержки
|
||||
- Единообразие кода
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Как начать внедрение
|
||||
|
||||
### Шаг 1: Тестирование компонента
|
||||
```bash
|
||||
# Компоненты уже созданы:
|
||||
# - templates/includes/components/page_header.html
|
||||
# - static/css/components/page-header.css
|
||||
# - templates/employees/employees_list_NEW_EXAMPLE.html (пример)
|
||||
```
|
||||
|
||||
### Шаг 2: Выбрать первую страницу
|
||||
Рекомендую начать с **employees_list.html** (самая простая)
|
||||
|
||||
### Шаг 3: Заменить шапку
|
||||
```django
|
||||
{# БЫЛО #}
|
||||
<div class="feed-header d-flex align-items-center">
|
||||
<i class="bi-people fs-2 text-primary"></i>
|
||||
<h2 class="title mb-0">Сотрудники</h2>
|
||||
...
|
||||
</div>
|
||||
|
||||
{# СТАЛО #}
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-people"
|
||||
title="Сотрудники"
|
||||
count=employees|length
|
||||
search_id="empFilter"
|
||||
search_placeholder="Поиск по ФИО, должности или отделу"
|
||||
create_url=create_url
|
||||
%}
|
||||
```
|
||||
|
||||
### Шаг 4: Добавить CSS
|
||||
```django
|
||||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" href="{% static 'css/components/page-header.css' %}">
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
### Шаг 5: Обновить views.py
|
||||
```python
|
||||
def employees_list(request):
|
||||
employees = Employee.objects.all()
|
||||
|
||||
# Добавить URL для кнопки создания
|
||||
create_url = None
|
||||
if request.user.has_perm('employees.add_employee'):
|
||||
create_url = reverse('employees:employee_create')
|
||||
|
||||
return render(request, 'employees/employees_list.html', {
|
||||
'employees': employees,
|
||||
'create_url': create_url,
|
||||
})
|
||||
```
|
||||
|
||||
### Шаг 6: Протестировать
|
||||
- ✅ Десктоп (Chrome, Firefox)
|
||||
- ✅ Планшет (768px)
|
||||
- ✅ Мобильный (375px)
|
||||
- ✅ Поиск работает
|
||||
- ✅ Кнопка создания работает
|
||||
- ✅ Sticky работает при скролле
|
||||
|
||||
---
|
||||
|
||||
## 📋 Чеклист внедрения
|
||||
|
||||
Для каждой страницы:
|
||||
|
||||
- [ ] Создать резервную копию файла
|
||||
- [ ] Добавить `page-header.css` в блок extra_css
|
||||
- [ ] Заменить старую шапку на `{% include "includes/components/page_header.html" %}`
|
||||
- [ ] Настроить параметры компонента
|
||||
- [ ] Обновить views.py (добавить create_url, scope и т.д.)
|
||||
- [ ] Переместить фильтры в collapse блок (если есть)
|
||||
- [ ] Удалить старую кнопку создания (если была в отдельной карточке)
|
||||
- [ ] Протестировать на всех разрешениях
|
||||
- [ ] Проверить JavaScript (поиск, фильтры)
|
||||
- [ ] Проверить accessibility (ARIA, клавиатура)
|
||||
- [ ] Закоммитить изменения
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Дизайн-токены
|
||||
|
||||
### Цвета
|
||||
```css
|
||||
--page-header-bg: color-mix(in srgb, var(--bs-body-bg) 85%, transparent);
|
||||
--page-header-border: color-mix(in srgb, var(--bs-border-color), transparent 50%);
|
||||
```
|
||||
|
||||
### Размеры
|
||||
```css
|
||||
--page-header-padding: var(--space-2) 0; /* 8px vertical */
|
||||
--page-header-margin: var(--space-3); /* 12px bottom */
|
||||
--page-title-size: 1.75rem; /* 28px */
|
||||
--page-create-btn-radius: 999px; /* pill shape */
|
||||
```
|
||||
|
||||
### Z-index слои
|
||||
```css
|
||||
--page-header-z: 10; /* Над контентом */
|
||||
--navbar-z: 100; /* Navbar выше всех */
|
||||
--modal-z: 1050; /* Модальные окна выше всех */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Настройка под специфичные страницы
|
||||
|
||||
### Без поиска
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
...
|
||||
no_search=True
|
||||
%}
|
||||
```
|
||||
|
||||
### Без кнопки создания
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
...
|
||||
no_create=True
|
||||
%}
|
||||
```
|
||||
|
||||
### С модальным окном создания
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
...
|
||||
create_modal="myModal"
|
||||
create_text="Создать элемент"
|
||||
%}
|
||||
```
|
||||
|
||||
### С переключателем области
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
...
|
||||
scope=scope
|
||||
scope_urls=scope_urls
|
||||
%}
|
||||
|
||||
{# В views.py #}
|
||||
scope_urls = {
|
||||
'mine': reverse('app:list') + '?view=mine',
|
||||
'all': reverse('app:list') + '?view=all',
|
||||
}
|
||||
```
|
||||
|
||||
### С фильтрами
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
...
|
||||
has_filters=True
|
||||
filters_target="myFilters"
|
||||
%}
|
||||
|
||||
<div class="collapse page-filters-collapse" id="myFilters">
|
||||
<div class="page-filters">
|
||||
<!-- Содержимое фильтров -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 Метрики успеха
|
||||
|
||||
После внедрения компонента можно ожидать:
|
||||
|
||||
1. **Код:**
|
||||
- -50% строк кода в шаблонах шапок
|
||||
- -30% дублирующегося CSS
|
||||
- Единая точка изменений
|
||||
|
||||
2. **Дизайн:**
|
||||
- 100% единообразие шапок
|
||||
- Улучшенная визуальная иерархия
|
||||
- Современный вид (iOS-стиль)
|
||||
|
||||
3. **UX:**
|
||||
- Улучшенная доступность (ARIA)
|
||||
- Более быстрый доступ к действиям
|
||||
- Меньше отвлекающих элементов
|
||||
|
||||
4. **Производительность:**
|
||||
- Меньше CSS для загрузки
|
||||
- Переиспользование стилей браузером
|
||||
- Оптимизированные селекторы
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Известные ограничения
|
||||
|
||||
1. **Backdrop-filter** не поддерживается в старых браузерах (IE11)
|
||||
- Решение: Fallback на обычный фон
|
||||
|
||||
2. **Sticky** может конфликтовать с другими sticky элементами
|
||||
- Решение: Правильные z-index значения
|
||||
|
||||
3. **Длинные заголовки** могут переноситься на мобильных
|
||||
- Решение: Используем text-truncate где нужно
|
||||
|
||||
---
|
||||
|
||||
## 💡 Будущие улучшения
|
||||
|
||||
1. **Breadcrumbs** в шапке для навигации
|
||||
2. **Экспорт данных** (кнопка справа в шапке)
|
||||
3. **Bulk actions** для множественного выбора
|
||||
4. **View modes** (список/сетка/карточки)
|
||||
5. **Сортировка** в шапке таблицы
|
||||
6. **Saved filters** (сохранённые фильтры)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Контакты
|
||||
|
||||
При возникновении вопросов:
|
||||
1. Смотрите `PLAN_PAGE_HEADER_STANDARDIZATION.md` (подробный план)
|
||||
2. Смотрите `templates/employees/employees_list_NEW_EXAMPLE.html` (рабочий пример)
|
||||
3. Смотрите комментарии в `page_header.html` (документация параметров)
|
||||
|
||||
---
|
||||
|
||||
**Статус:** ✅ Готово к внедрению
|
||||
**Дата создания:** 4 ноября 2025 г.
|
||||
**Версия:** 1.0.0
|
||||
@ -1,186 +0,0 @@
|
||||
# Phase 6: CSS Refactoring Summary
|
||||
|
||||
## Objective
|
||||
Eliminate CSS duplication and standardize naming conventions across all component files.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Created Common CSS File
|
||||
- **File**: `static/css/components/common.css` (245 lines)
|
||||
- **Purpose**: Centralized shared classes to eliminate duplication
|
||||
- **Contents**:
|
||||
- `.btn-icon` (36px icon buttons with variants: --primary, --danger, --success)
|
||||
- `.badge-acked`, `.badge-status` (status badges with state variants)
|
||||
- `.avatar` with size variants (xs/sm/md/lg/xl)
|
||||
- `.feed-author`, `.feed-sub` (feed metadata)
|
||||
- `.chip`, `.chip-remove` (chip components)
|
||||
- `.text-truncate-2`, `.text-truncate-3`, `.custom-scrollbar` (utilities)
|
||||
|
||||
### 2. Removed Duplicate Files
|
||||
- **Deleted**: `static/css/components/feed-header.css`
|
||||
- **Reason**: Complete duplicate of functionality in `feed-cards.css`
|
||||
|
||||
### 3. Cleaned Component Files (Removed ~200 Lines of Duplication)
|
||||
|
||||
#### `base-app.css`
|
||||
- ❌ Removed duplicate variables: `--sidebar-w`, `--rightbar-w`, `--navbar-h`, `--feed-radius`, `--feed-shadow`
|
||||
- ✅ Now uses variables from `variables.css`
|
||||
|
||||
#### `document-list.css`
|
||||
- ❌ Removed: `.btn-icon` (36 lines) → uses `common.css`
|
||||
- ❌ Removed: `.badge-acked` (15 lines) → uses `common.css`
|
||||
- ✅ Added dependency comment header
|
||||
|
||||
#### `employee-list.css`
|
||||
- ❌ Removed: `.feed-author`, `.feed-sub` duplicate definitions
|
||||
- ✅ Kept component-specific overrides (`#empList .feed-author`)
|
||||
|
||||
#### `department-list.css`
|
||||
- ❌ Removed: `.feed-header`, `.feed-author`, `.feed-sub` (14 lines)
|
||||
- ✅ Now imports from `common.css` + `feed-cards.css`
|
||||
|
||||
#### `ios-components.css`
|
||||
- ❌ Removed: `.btn-icon` with all variants (56 lines)
|
||||
- ❌ Removed: `.chip` base definition (10 lines)
|
||||
- ✅ Added dependency comment explaining `common.css` usage
|
||||
|
||||
#### `request-list.css`
|
||||
- ❌ Removed: `.badge-status` duplicate definition
|
||||
- ✅ Added dependency header
|
||||
|
||||
### 4. Unified Variable Naming in `variables.css`
|
||||
|
||||
#### Size Variables (Consistency)
|
||||
- `--navbar-height` → `--navbar-h` (60px)
|
||||
- `--sidebar-width` → `--sidebar-w` (190px)
|
||||
- `--rightbar-width` → `--rightbar-w` (350px)
|
||||
|
||||
#### Border Radii (Complete Scale)
|
||||
- Added: `--radius-xs` (4px)
|
||||
- Added: `--radius-2xl` (24px)
|
||||
- Existing: `--radius-sm` (8px), `--radius-md` (10px), `--radius-lg` (14px), `--radius-xl` (18px)
|
||||
|
||||
#### Component-Specific Aliases
|
||||
**Feed Components:**
|
||||
- `--feed-gap: 0.75rem` (unified gap spacing)
|
||||
|
||||
**iOS Components:**
|
||||
- `--ios-overlay-bg: rgba(0, 0, 0, 0.4)`
|
||||
- `--ios-sheet-radius: 24px 24px 0 0`
|
||||
- `--ios-grip-w: 36px` (was `--ios-grip-width`)
|
||||
- `--ios-grip-h: 5px` (was `--ios-grip-height`)
|
||||
- `--ios-search-radius: var(--radius-md)` (was fixed 10px)
|
||||
|
||||
**Avatars:**
|
||||
- `--avatar-xs: 28px`
|
||||
- `--avatar-sm: 36px`
|
||||
- `--avatar-md: 44px`
|
||||
- `--avatar-lg: 64px`
|
||||
- `--avatar-xl: 96px`
|
||||
|
||||
#### Shadow System (Complete Hierarchy)
|
||||
- `--shadow-xs`: Minimal shadow for subtle depth
|
||||
- `--shadow-sm`: Small elements (chips, badges)
|
||||
- `--shadow-md`: Standard cards
|
||||
- `--shadow-lg`: Elevated cards on hover
|
||||
- `--shadow-xl`: Modal overlays
|
||||
|
||||
### 5. Updated Templates (6 Files)
|
||||
|
||||
#### Import Order (Standardized)
|
||||
```html
|
||||
<link rel="stylesheet" href="{% static 'css/variables.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/components/common.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'css/components/feed-cards.css' %}">
|
||||
<!-- Component-specific CSS -->
|
||||
```
|
||||
|
||||
#### Modified Templates
|
||||
1. **base.html** - Added `common.css` import (affects all pages)
|
||||
2. **search/results.html** - Changed `feed-header.css` → `feed-cards.css`
|
||||
3. **communications/chat_list.html** - Changed `feed-header.css` → `feed-cards.css`
|
||||
4. **documents/document_list.html** - Added `common.css` + reordered imports
|
||||
5. **employees/employees_list.html** - Added `common.css` + reordered imports
|
||||
6. **employees/department_list.html** - Added `common.css` + reordered imports
|
||||
|
||||
### 6. Updated Import Structure
|
||||
|
||||
#### `static/css/components/index.css`
|
||||
```css
|
||||
@import 'variables.css';
|
||||
@import 'common.css'; /* NEW: shared classes */
|
||||
@import 'feed-cards.css';
|
||||
@import 'ios-search.css';
|
||||
@import 'ios-components.css';
|
||||
```
|
||||
|
||||
## Results
|
||||
|
||||
### Metrics
|
||||
- **Files Modified**: 16
|
||||
- **Files Created**: 1 (`common.css`)
|
||||
- **Files Deleted**: 1 (`feed-header.css`)
|
||||
- **Lines Removed**: ~200 (duplication eliminated)
|
||||
- **Lines Added**: 245 (`common.css`)
|
||||
- **Net Change**: +45 lines (centralized in one place)
|
||||
- **Duplication Rate**: 40% → 0% for shared classes
|
||||
|
||||
### Benefits
|
||||
1. **DRY Principle**: All shared classes defined once in `common.css`
|
||||
2. **Maintainability**: Changes to button/badge styles affect all components
|
||||
3. **Consistency**: Unified naming conventions across all files
|
||||
4. **Performance**: Reduced total CSS size after compression
|
||||
5. **Documentation**: Clear dependency headers in all component files
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```
|
||||
variables.css (base variables)
|
||||
↓
|
||||
common.css (shared classes)
|
||||
↓
|
||||
├── feed-cards.css (base feed system)
|
||||
├── ios-components.css (iOS-style UI)
|
||||
├── document-list.css (document-specific)
|
||||
├── employee-list.css (employee-specific)
|
||||
├── department-list.css (department-specific)
|
||||
├── request-list.css (request-specific)
|
||||
└── base-app.css (app-wide layout)
|
||||
```
|
||||
|
||||
## Component-Specific CSS Scope
|
||||
|
||||
Each component file now contains ONLY:
|
||||
- Component-specific layout/behavior
|
||||
- Overrides for specific contexts (e.g., `#empList .feed-author`)
|
||||
- Unique styles not shared with other components
|
||||
|
||||
Shared styles (buttons, badges, avatars, chips, utilities) are in `common.css`.
|
||||
|
||||
## Validation
|
||||
|
||||
### Testing Checklist
|
||||
- ✅ All 21 CSS component files scanned for duplication
|
||||
- ✅ Variable naming unified across all files
|
||||
- ✅ Template imports updated and verified
|
||||
- ✅ Dependency headers added to modified files
|
||||
- ✅ No broken references to deleted files
|
||||
- ✅ Import order standardized (variables → common → components)
|
||||
|
||||
### Known Non-Issues
|
||||
- `rightbar-calendar.css` and `rightbar-calendar-fullcalendar.css` are both used in different templates (not duplicates)
|
||||
- `webkit-line-clamp` warnings are non-critical (standard CSS property)
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Run Tests**: Execute full test suite to validate changes
|
||||
2. **Visual QA**: Check all pages render correctly
|
||||
3. **Performance**: Measure CSS load time improvements
|
||||
4. **Documentation**: Update `static/css/README.md` with new architecture
|
||||
|
||||
---
|
||||
|
||||
**Phase 6 Status**: ✅ **COMPLETE**
|
||||
**Date**: 2024
|
||||
**Duplication Eliminated**: ~200 lines
|
||||
**Architecture**: Clean, maintainable, DRY-compliant
|
||||
@ -1,623 +0,0 @@
|
||||
# План стандартизации шапок страниц
|
||||
|
||||
## 📋 Обзор
|
||||
|
||||
Создан универсальный компонент `page_header.html` для стандартизации всех шапок страниц списков в приложении.
|
||||
|
||||
### Компоненты
|
||||
- **HTML**: `templates/includes/components/page_header.html`
|
||||
- **CSS**: `static/css/components/page-header.css`
|
||||
|
||||
### Особенности компонента
|
||||
✅ Полупрозрачный фон с backdrop-filter (как в iOS)
|
||||
✅ Sticky позиционирование (прилипает к верху при скролле)
|
||||
✅ Вытянутая кнопка создания на десктопе, круглая иконка на мобильных
|
||||
✅ Поиск iOS-стиль (интегрируется с существующим ios-search.css)
|
||||
✅ Кнопка раскрытия фильтров (collapse toggle)
|
||||
✅ Переключатель области "Мои/Все" (опционально)
|
||||
✅ Адаптивная верстка (flex-wrap на мобильных)
|
||||
✅ Счётчик элементов
|
||||
✅ Слот для дополнительных кнопок
|
||||
|
||||
---
|
||||
|
||||
## 📊 Анализ текущих шапок
|
||||
|
||||
### 1. **employees_list.html** - Список сотрудников
|
||||
**Текущее состояние:**
|
||||
```django
|
||||
<div class="feed-header d-flex align-items-center">
|
||||
<i class="bi-people fs-2 text-primary"></i>
|
||||
<h2 class="title mb-0">Сотрудники</h2>
|
||||
<span class="ms-2 small text-secondary">Всего: {{ employees|length }}</span>
|
||||
<div class="ms-auto ios-search">
|
||||
<i class="bi-search"></i>
|
||||
<input id="empFilter" type="search" ...>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Что нужно:**
|
||||
- ✅ Иконка
|
||||
- ✅ Заголовок
|
||||
- ✅ Счётчик
|
||||
- ✅ Поиск
|
||||
- ❌ Кнопка создания (есть, но в отдельной карточке)
|
||||
- ❌ Фильтры (нет)
|
||||
- ❌ Переключатель Мои/Все (нет)
|
||||
- ❌ Полупрозрачный фон
|
||||
|
||||
**Приоритет:** 🔴 Высокий
|
||||
|
||||
---
|
||||
|
||||
### 2. **department_list.html** - Список отделов
|
||||
**Текущее состояние:**
|
||||
```django
|
||||
<div class="feed-header d-flex align-items-center">
|
||||
<i class="bi-diagram-3 fs-2 text-primary"></i>
|
||||
<h2 class="title mb-0">Отделы</h2>
|
||||
<span class="ms-2 small text-secondary">Всего: {{ departments|length }}</span>
|
||||
<div class="ms-auto ios-search">...</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Что нужно:**
|
||||
- ✅ Иконка
|
||||
- ✅ Заголовок
|
||||
- ✅ Счётчик
|
||||
- ✅ Поиск
|
||||
- ❌ Кнопка создания (есть, но в отдельной карточке)
|
||||
- ❌ Фильтры (нет)
|
||||
- ❌ Переключатель Мои/Все (нет)
|
||||
- ❌ Полупрозрачный фон
|
||||
|
||||
**Приоритет:** 🔴 Высокий
|
||||
|
||||
---
|
||||
|
||||
### 3. **document_list.html** - Список документов
|
||||
**Текущее состояние:**
|
||||
```django
|
||||
<div class="ios-header feed-header d-flex align-items-center gap-2 flex-wrap">
|
||||
<i class="bi-file-earmark-text fs-2 text-primary"></i>
|
||||
<h2 class="title ios-title mb-0">Документы</h2>
|
||||
<div class="ms-auto d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="btn-group scope-switch">
|
||||
<a href="{{ scope_urls.mine }}" class="btn ...">Мои</a>
|
||||
<a href="{{ scope_urls.all }}" class="btn ...">Все</a>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary d-flex align-items-center gap-2"
|
||||
data-bs-toggle="modal" data-bs-target="#docCreateModal">
|
||||
<i class="bi-plus-lg"></i><span>Создать документ</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Что нужно:**
|
||||
- ✅ Иконка
|
||||
- ✅ Заголовок
|
||||
- ✅ Переключатель Мои/Все
|
||||
- ✅ Кнопка создания (модальное окно)
|
||||
- ✅ Полупрозрачный фон (.ios-header)
|
||||
- ❌ Поиск (нет)
|
||||
- ❌ Фильтры (нет)
|
||||
- ⚠️ Кнопка НЕ вытянутая (текст всегда виден)
|
||||
|
||||
**Приоритет:** 🟡 Средний (почти готово)
|
||||
|
||||
---
|
||||
|
||||
### 4. **request_list_full.html** - Список заявлений
|
||||
**Текущее состояние:**
|
||||
```django
|
||||
<div class="ios-header feed-header d-flex align-items-center gap-2 flex-wrap">
|
||||
<i class="bi-clipboard-check fs-2 text-primary"></i>
|
||||
<h2 class="title ios-title mb-0">Заявления</h2>
|
||||
<div class="ms-auto d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="btn-group scope-switch">...</div>
|
||||
<button type="button" class="btn btn-primary d-flex align-items-center gap-2"
|
||||
data-bs-toggle="modal" data-bs-target="#reqCreateModal">
|
||||
<i class="bi-plus-lg"></i><span>Новое заявление</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Фильтры в отдельном блоке ниже -->
|
||||
<form class="feed-new mb-3" method="get">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col-12 col-sm-4">
|
||||
<select name="type" class="form-select">...</select>
|
||||
</div>
|
||||
...
|
||||
</div>
|
||||
</form>
|
||||
```
|
||||
|
||||
**Что нужно:**
|
||||
- ✅ Иконка
|
||||
- ✅ Заголовок
|
||||
- ✅ Переключатель Мои/Все
|
||||
- ✅ Кнопка создания
|
||||
- ✅ Фильтры (но не скрываемые)
|
||||
- ✅ Полупрозрачный фон
|
||||
- ❌ Поиск (нет)
|
||||
- ❌ Кнопка toggle фильтров (фильтры всегда видны)
|
||||
|
||||
**Приоритет:** 🟡 Средний
|
||||
|
||||
---
|
||||
|
||||
### 5. **feed_list.html** - Лента новостей
|
||||
**Текущее состояние:**
|
||||
```django
|
||||
<div class="feed-header d-flex align-items-center gap-3">
|
||||
<i class="bi-megaphone fs-2 text-primary"></i>
|
||||
<h2 class="title mb-0">Лента</h2>
|
||||
{% if user.is_authenticated and user.is_staff %}
|
||||
<a class="btn btn-primary ms-auto" href="{% url 'feed:post_create' %}?type=company">
|
||||
<i class="bi-plus-circle me-1"></i> Новая публикация
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Что нужно:**
|
||||
- ✅ Иконка
|
||||
- ✅ Заголовок
|
||||
- ✅ Кнопка создания (переход на другую страницу)
|
||||
- ❌ Поиск (нет)
|
||||
- ❌ Фильтры (нет)
|
||||
- ❌ Счётчик (нет)
|
||||
- ❌ Полупрозрачный фон
|
||||
|
||||
**Приоритет:** 🟢 Низкий (специфичная страница)
|
||||
|
||||
---
|
||||
|
||||
### 6. **chat_list.html** - Список чатов
|
||||
**Текущее состояние:**
|
||||
```django
|
||||
<div class="feed-header d-flex align-items-center">
|
||||
<i class="bi-chat-dots fs-2 text-primary"></i>
|
||||
<h2 class="title mb-0">Мои чаты</h2>
|
||||
<div class="ms-auto ios-search">
|
||||
<i class="bi-search"></i>
|
||||
<input type="search" id="chatSearch" ...>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Фильтры ниже -->
|
||||
<div class="feed-new mb-3">
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="btn-group" role="group">
|
||||
<input type="radio" class="btn-check" name="chatFilter" id="fltAll" checked>
|
||||
<label class="btn btn-ghost" for="fltAll">Все</label>
|
||||
...
|
||||
</div>
|
||||
<a href="..." class="btn btn-ghost ms-auto">
|
||||
<i class="bi-person-plus"></i> Новый чат
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Что нужно:**
|
||||
- ✅ Иконка
|
||||
- ✅ Заголовок
|
||||
- ✅ Поиск
|
||||
- ✅ Фильтры (но не скрываемые, radio buttons)
|
||||
- ⚠️ Кнопка создания (но специфичная - "Новый чат")
|
||||
- ❌ Счётчик (нет)
|
||||
- ❌ Полупрозрачный фон
|
||||
|
||||
**Приоритет:** 🟡 Средний
|
||||
|
||||
---
|
||||
|
||||
### 7. **search/results.html** - Результаты поиска
|
||||
**Текущее состояние:**
|
||||
```django
|
||||
<div class="feed-header d-flex align-items-center">
|
||||
<i class="bi-search fs-2 text-primary"></i>
|
||||
<h2 class="title mb-0">Результаты поиска</h2>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Что нужно:**
|
||||
- ✅ Иконка
|
||||
- ✅ Заголовок
|
||||
- ❌ Все остальное (специфичная страница)
|
||||
|
||||
**Приоритет:** 🟢 Низкий (специфичная страница)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 План внедрения
|
||||
|
||||
### Фаза 1: Простые списки (Приоритет 🔴)
|
||||
|
||||
#### 1.1. employees_list.html
|
||||
```django
|
||||
{% load static %}
|
||||
|
||||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" href="{% static 'css/components/page-header.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row gx-0">
|
||||
<div class="col-12">
|
||||
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-people"
|
||||
title="Сотрудники"
|
||||
count=employees|length
|
||||
count_label="Всего:"
|
||||
search_id="empFilter"
|
||||
search_placeholder="Поиск по ФИО, должности или отделу"
|
||||
create_url=create_url
|
||||
create_text="Создать профиль"
|
||||
create_icon="bi-person-plus"
|
||||
%}
|
||||
|
||||
<!-- Далее список... -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
**Изменения в views.py:**
|
||||
```python
|
||||
context = {
|
||||
'employees': employees,
|
||||
'create_url': reverse('employees:employee_create') if has_perm else None,
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2. department_list.html
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-diagram-3"
|
||||
title="Отделы"
|
||||
count=departments|length
|
||||
search_id="deptFilter"
|
||||
search_placeholder="Поиск по названию или руководителю"
|
||||
create_url=create_url
|
||||
create_text="Создать отдел"
|
||||
%}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Фаза 2: Списки с областью видимости (Приоритет 🟡)
|
||||
|
||||
#### 2.1. document_list.html
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-file-earmark-text"
|
||||
title="Документы"
|
||||
scope=scope
|
||||
scope_urls=scope_urls
|
||||
create_modal="docCreateModal"
|
||||
create_text="Создать документ"
|
||||
has_filters=True
|
||||
filters_target="docFilters"
|
||||
%}
|
||||
|
||||
<!-- Блок фильтров -->
|
||||
{% if has_filters %}
|
||||
<div class="collapse page-filters-collapse" id="docFilters">
|
||||
<div class="page-filters">
|
||||
<!-- Фильтры по типу документа, дате и т.д. -->
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- Добавить фильтры (тип, дата загрузки, отправитель)
|
||||
- Фильтры скрываются по умолчанию, открываются кнопкой
|
||||
- Добавить поиск по названию/описанию
|
||||
|
||||
#### 2.2. request_list_full.html
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-clipboard-check"
|
||||
title="Заявления"
|
||||
scope=scope
|
||||
scope_urls=scope_urls
|
||||
create_modal="reqCreateModal"
|
||||
create_text="Новое заявление"
|
||||
has_filters=True
|
||||
filters_target="reqFilters"
|
||||
%}
|
||||
|
||||
<!-- Существующие фильтры переносим в collapse -->
|
||||
<div class="collapse page-filters-collapse" id="reqFilters">
|
||||
<div class="page-filters">
|
||||
<form method="get">
|
||||
<div class="row g-2">
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<label class="form-label">Тип</label>
|
||||
<select name="type" class="form-select">...</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 col-lg-3">
|
||||
<label class="form-label">Статус</label>
|
||||
<select name="status" class="form-select">...</select>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 d-flex align-items-end gap-2">
|
||||
<button class="btn btn-primary" type="submit">Применить</button>
|
||||
<a class="btn btn-outline-secondary" href="?view={{ scope }}">Сбросить</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- Фильтры перемещаются в collapse блок
|
||||
- Добавить поиск по названию заявления
|
||||
- Кнопка фильтров в шапке
|
||||
|
||||
---
|
||||
|
||||
### Фаза 3: Специфичные страницы (Приоритет 🟡)
|
||||
|
||||
#### 3.1. chat_list.html
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-chat-dots"
|
||||
title="Мои чаты"
|
||||
search_id="chatSearch"
|
||||
search_placeholder="Поиск по названию или участникам"
|
||||
create_url=employees_url
|
||||
create_text="Новый чат"
|
||||
create_icon="bi-person-plus"
|
||||
has_filters=True
|
||||
filters_target="chatFilters"
|
||||
%}
|
||||
|
||||
<!-- Фильтры чатов -->
|
||||
<div class="collapse page-filters-collapse" id="chatFilters">
|
||||
<div class="page-filters">
|
||||
<div class="btn-group w-100" role="group">
|
||||
<input type="radio" class="btn-check" name="chatFilter" id="fltAll" checked>
|
||||
<label class="btn btn-outline-secondary" for="fltAll">Все</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="chatFilter" id="fltGlobal">
|
||||
<label class="btn btn-outline-secondary" for="fltGlobal">Глобальный</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="chatFilter" id="fltDepartment">
|
||||
<label class="btn btn-outline-secondary" for="fltDepartment">Отделы</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="chatFilter" id="fltPrivate">
|
||||
<label class="btn btn-outline-secondary" for="fltPrivate">Личные</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- Radio-button фильтры переносим в collapse
|
||||
- Скрываются по умолчанию
|
||||
|
||||
---
|
||||
|
||||
## 📝 Чеклист внедрения
|
||||
|
||||
### Для каждой страницы:
|
||||
|
||||
- [ ] **1. Добавить CSS**
|
||||
```django
|
||||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" href="{% static 'css/components/page-header.css' %}">
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
- [ ] **2. Заменить существующую шапку**
|
||||
- Удалить старый `.feed-header` или `.ios-header`
|
||||
- Вставить `{% include "includes/components/page_header.html" with ... %}`
|
||||
|
||||
- [ ] **3. Настроить параметры**
|
||||
- Определить нужные параметры (icon, title, search_id и т.д.)
|
||||
- Передать через `with`
|
||||
|
||||
- [ ] **4. Обновить views.py**
|
||||
- Добавить `create_url` в context (если нужно)
|
||||
- Добавить `scope` и `scope_urls` (если есть Мои/Все)
|
||||
|
||||
- [ ] **5. Переместить фильтры в collapse**
|
||||
- Если есть фильтры, обернуть в `<div class="collapse" id="...">`
|
||||
- Добавить `has_filters=True` и `filters_target="..."`
|
||||
|
||||
- [ ] **6. Обновить JavaScript**
|
||||
- Если есть поиск - проверить, что ID совпадает с `search_id`
|
||||
- Если есть фильтры - добавить обработчики для collapse
|
||||
|
||||
- [ ] **7. Тестирование**
|
||||
- Проверить на десктопе (1920x1080)
|
||||
- Проверить на планшете (768px)
|
||||
- Проверить на мобильном (375px)
|
||||
- Проверить все интерактивные элементы (поиск, фильтры, создание)
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Стилистические правила
|
||||
|
||||
### 1. Иконки Bootstrap Icons
|
||||
```
|
||||
Сотрудники: bi-people
|
||||
Отделы: bi-diagram-3
|
||||
Документы: bi-file-earmark-text
|
||||
Заявления: bi-clipboard-check
|
||||
Лента: bi-megaphone
|
||||
Чаты: bi-chat-dots
|
||||
Календарь: bi-calendar-event
|
||||
Поиск: bi-search
|
||||
```
|
||||
|
||||
### 2. Текст кнопок создания
|
||||
```
|
||||
Сотрудники: "Создать профиль"
|
||||
Отделы: "Создать отдел"
|
||||
Документы: "Создать документ"
|
||||
Заявления: "Новое заявление"
|
||||
Публикации: "Новая публикация"
|
||||
Чаты: "Новый чат"
|
||||
```
|
||||
|
||||
### 3. Placeholders для поиска
|
||||
```
|
||||
Сотрудники: "Поиск по ФИО, должности или отделу"
|
||||
Отделы: "Поиск по названию или руководителю"
|
||||
Документы: "Поиск по названию или описанию"
|
||||
Заявления: "Поиск по типу или статусу"
|
||||
Чаты: "Поиск по названию или участникам"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Миграция существующего кода
|
||||
|
||||
### Маппинг классов
|
||||
|
||||
| Старый класс | Новый класс | Примечание |
|
||||
|--------------|-------------|------------|
|
||||
| `.feed-header` | `.page-header` | Основной контейнер |
|
||||
| `.ios-header` | `.page-header` | Уже с полупрозрачностью |
|
||||
| `.title` | `.page-title` | Заголовок |
|
||||
| `.ios-title` | `.page-title` | То же самое |
|
||||
| `.scope-switch` | `.page-scope-switch` | Переключатель Мои/Все |
|
||||
| `.feed-new` (для фильтров) | `.page-filters` | Блок фильтров |
|
||||
|
||||
### Удаляемые блоки
|
||||
|
||||
```django
|
||||
<!-- СТАРЫЙ КОД - УДАЛИТЬ -->
|
||||
{% if perms.employees.add_employee %}
|
||||
<div class="feed-card mb-2">
|
||||
<a href="{% url 'employees:employee_create' %}" class="dept-row ...">
|
||||
<span class="feed-author">Создать профиль</span>
|
||||
<i class="bi-plus-lg"></i>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Кнопка создания теперь в шапке -->
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 Временные оценки
|
||||
|
||||
| Страница | Сложность | Время | Фаза |
|
||||
|----------|-----------|-------|------|
|
||||
| employees_list.html | Простая | 30 мин | 1 |
|
||||
| department_list.html | Простая | 30 мин | 1 |
|
||||
| document_list.html | Средняя | 1 час | 2 |
|
||||
| request_list_full.html | Средняя | 1 час | 2 |
|
||||
| chat_list.html | Средняя | 1 час | 3 |
|
||||
| feed_list.html | Простая | 30 мин | 3 |
|
||||
|
||||
**Итого:** ~4.5 часа чистой разработки
|
||||
|
||||
**С тестированием:** ~6-7 часов
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Примеры использования
|
||||
|
||||
### Пример 1: Простой список с поиском
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-people"
|
||||
title="Сотрудники"
|
||||
count=employees|length
|
||||
search_id="empFilter"
|
||||
search_placeholder="Поиск сотрудников..."
|
||||
create_url=create_url
|
||||
%}
|
||||
```
|
||||
|
||||
### Пример 2: С областью видимости и фильтрами
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-file-earmark-text"
|
||||
title="Документы"
|
||||
scope=scope
|
||||
scope_urls=scope_urls
|
||||
search_id="docSearch"
|
||||
search_placeholder="Поиск документов..."
|
||||
has_filters=True
|
||||
filters_target="docFilters"
|
||||
create_modal="docCreateModal"
|
||||
create_text="Создать документ"
|
||||
%}
|
||||
```
|
||||
|
||||
### Пример 3: Без поиска, с модальным окном
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-clipboard-check"
|
||||
title="Заявления"
|
||||
no_search=True
|
||||
create_modal="reqCreateModal"
|
||||
create_text="Новое заявление"
|
||||
%}
|
||||
```
|
||||
|
||||
### Пример 4: Только заголовок (страница деталей)
|
||||
```django
|
||||
{% include "includes/components/page_header.html" with
|
||||
icon="bi-person"
|
||||
title=employee.get_full_name
|
||||
no_search=True
|
||||
no_create=True
|
||||
%}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Преимущества нового компонента
|
||||
|
||||
1. **Единообразие** - все страницы списков выглядят одинаково
|
||||
2. **Адаптивность** - автоматическая работа на всех устройствах
|
||||
3. **Современный дизайн** - полупрозрачный фон, плавные анимации
|
||||
4. **Простота использования** - один include вместо 50+ строк кода
|
||||
5. **Гибкость** - опциональные параметры для разных сценариев
|
||||
6. **Производительность** - меньше CSS, переиспользование стилей
|
||||
7. **Поддержка** - исправления в одном месте применяются везде
|
||||
8. **Доступность** - ARIA-атрибуты, семантическая разметка
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Следующие шаги
|
||||
|
||||
1. ✅ Создать компоненты (page_header.html, page-header.css)
|
||||
2. ⏳ Протестировать на одной странице (employees_list.html)
|
||||
3. ⏳ Получить обратную связь от команды
|
||||
4. ⏳ Внедрить в остальные страницы согласно плану
|
||||
5. ⏳ Обновить документацию
|
||||
6. ⏳ Удалить устаревший CSS (.feed-header, .ios-header)
|
||||
|
||||
---
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
После внедрения обновить:
|
||||
- `README.md` - добавить секцию "Компоненты UI"
|
||||
- `COMPONENTS.md` - создать документацию по компонентам
|
||||
- Storybook (если используется) - добавить примеры
|
||||
|
||||
---
|
||||
|
||||
**Создано:** 4 ноября 2025 г.
|
||||
**Автор:** GitHub Copilot
|
||||
**Статус:** 🟡 В разработке
|
||||
@ -1,204 +0,0 @@
|
||||
# REFACTORING COMPLETE: Summary
|
||||
|
||||
**Дата завершения**: январь 2025
|
||||
**Версия проекта**: 2.1-templates-refactored
|
||||
**Статус**: ✅ Фазы 0-5 завершены (85%), Фаза 6 документация завершена
|
||||
|
||||
---
|
||||
|
||||
## Выполненные задачи
|
||||
|
||||
### ✅ Фаза 0: Инфраструктура
|
||||
- Создана структура папок: `static/js/utils/`, `static/js/components/`, `static/css/components/`
|
||||
- Настроены индексные файлы для экспорта модулей
|
||||
|
||||
### ✅ Фаза 1: Устранение дубликатов
|
||||
- Удалены неиспользуемые файлы (3 файла)
|
||||
- Объединены папки `requests/` и `requests_app/`
|
||||
- **Результат**: 61 → 58 HTML файлов
|
||||
|
||||
### ✅ Фаза 2: JavaScript утилиты
|
||||
**Созданы модули**:
|
||||
- `stringUtils.js` (115 строк): esc(), norm(), escAttr(), truncate(), getInitials()
|
||||
- `timing.js` (173 строки): debounce(), throttle(), delay(), poll(), once()
|
||||
- `scroll.js` (205 строк): smoothScrollTo(), scrollToTop(), isElementInViewport()
|
||||
- `listFilter.js` (279 строк): универсальная фильтрация списков
|
||||
|
||||
**Интеграция**:
|
||||
- Все утилиты импортированы в `base.html`
|
||||
- Экспорт в `window` для обратной совместимости
|
||||
- Использовано в 20+ шаблонах
|
||||
|
||||
### ✅ Фаза 3: CSS extraction
|
||||
**Созданы компоненты** (22 файла):
|
||||
- `chat-detail.css`, `department-list.css`, `department-sidebar.css`
|
||||
- `document-list.css`, `employee-detail.css`, `feed-cards.css`
|
||||
- `login.css`, `logout.css`, `navbar.css`, `password-forms.css`
|
||||
- `register.css`, `request-list.css`, `rightbar-calendar.css`
|
||||
- `search-results.css`, `sidebar.css`, `team-wheel.css`
|
||||
- И другие...
|
||||
|
||||
**Общие стили**:
|
||||
- `common.css` (245 строк): переиспользуемые классы (45+)
|
||||
- `variables.css`: унифицированные CSS-переменные
|
||||
|
||||
**Результат**:
|
||||
- **0 встроенных `<style>` блоков** во всех шаблонах ✅
|
||||
- Удалено ~1500 строк дублирующихся стилей
|
||||
|
||||
### ✅ Фаза 4: JavaScript extraction
|
||||
**Созданы компоненты**:
|
||||
- `teamWheel.js` (308 строк): вращающийся виджет команды
|
||||
- `calendarWidget.js` (1002 строки): FullCalendar + CRUD событий
|
||||
|
||||
**Результат**:
|
||||
- `rightbar_calendar.html`: 996 → 237 строк (**76% сокращение**)
|
||||
- Модули экспортированы через `components/index.js` (v1.1.0)
|
||||
|
||||
### ✅ Фаза 5: HTML decomposition (частично)
|
||||
**Декомпозированные файлы**:
|
||||
1. `_employee_edit.html`: 1391 → 24 строки (**98.3% сокращение**)
|
||||
2. `department_detail.html`: 1025 → 46 строк (**95.5% сокращение**)
|
||||
|
||||
**Созданы компоненты**:
|
||||
- `employees/components/department_info.html`
|
||||
- `employees/components/department_sidebar.html`
|
||||
- `employees/components/department_modals.html`
|
||||
|
||||
### ✅ Фаза 6: Документация
|
||||
**Созданные файлы**:
|
||||
- `templates/TEMPLATES_GUIDE.md` (511 строк) - полное руководство
|
||||
- `static/css/README.md` - документация CSS
|
||||
- `PHASE6_SUMMARY.md` - итоги CSS рефакторинга
|
||||
- Обновлён `REFACTORING_PLAN.md`
|
||||
|
||||
---
|
||||
|
||||
## Метрики успеха
|
||||
|
||||
### Код
|
||||
|
||||
| Метрика | До | После | Улучшение |
|
||||
|---------|-----|-------|-----------|
|
||||
| HTML файлов | 61 | 58 | -3 файла |
|
||||
| Встроенного CSS | ~1500 строк | **0 строк** | **-100%** ✅ |
|
||||
| Встроенного JavaScript | ~4500 строк | ~200 строк | **-95%** ✅ |
|
||||
| Файлов > 1000 строк | 3 | **0** | **-100%** ✅ |
|
||||
| CSS компонентов | 0 | 22 | +22 файла |
|
||||
| JS модулей | 0 | 6 | +6 файлов |
|
||||
|
||||
### Декомпозиция файлов
|
||||
|
||||
| Файл | Было строк | Стало строк | Сокращение |
|
||||
|------|-----------|-------------|------------|
|
||||
| `_employee_edit.html` | 1391 | 24 | **-98.3%** |
|
||||
| `department_detail.html` | 1025 | 46 | **-95.5%** |
|
||||
| `rightbar_calendar.html` | 996 | 237 | **-76.2%** |
|
||||
| **ИТОГО** | **3412** | **307** | **-91.0%** |
|
||||
|
||||
### Качество
|
||||
|
||||
✅ **Переиспользование кода**:
|
||||
- 45+ общих CSS классов в `common.css`
|
||||
- 14 JavaScript утилит
|
||||
- 3 крупных компонента (ListFilter, TeamWheel, CalendarWidget)
|
||||
|
||||
✅ **Поддерживаемость**:
|
||||
- Каждый компонент задокументирован
|
||||
- Понятная структура папок
|
||||
- Единообразное именование
|
||||
|
||||
✅ **Производительность**:
|
||||
- Браузер кеширует статические файлы
|
||||
- Параллельная загрузка модулей
|
||||
- Уменьшен размер HTML-страниц
|
||||
|
||||
---
|
||||
|
||||
## Git коммиты
|
||||
|
||||
```
|
||||
b779b17 refactor(css): Phase 6 - Eliminate duplication and unify naming
|
||||
f80d81e docs(css): Update README with Phase 6 architecture
|
||||
9234001 chore: remove obsolete documentation files
|
||||
9417ca5 refactor(js): Extract calendar widget to separate module
|
||||
ef1383b docs: Create comprehensive templates guide
|
||||
```
|
||||
|
||||
**Всего коммитов в рефакторинге**: 29+
|
||||
**Git tag**: `v2.1-templates-refactored`
|
||||
|
||||
---
|
||||
|
||||
## Оставшиеся задачи (опционально)
|
||||
|
||||
### Низкий приоритет:
|
||||
- [ ] Декомпозировать `rightbar_calendar.html` HTML структуру на компоненты
|
||||
- [ ] Создать визуальные регрессионные тесты
|
||||
- [ ] Минифицировать CSS/JS для production
|
||||
- [ ] Создать Storybook для компонентов
|
||||
|
||||
### Можно не делать:
|
||||
- Текущая структура уже значительно улучшена
|
||||
- Все критические проблемы устранены
|
||||
- Код переиспользуется и легко поддерживается
|
||||
|
||||
---
|
||||
|
||||
## Рекомендации для дальнейшей разработки
|
||||
|
||||
### При создании новых шаблонов:
|
||||
|
||||
1. **Структура**:
|
||||
```django
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block extra_css %}
|
||||
<link rel="stylesheet" href="{% static 'css/pages/my-page.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{# Основной контент #}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script type="module">
|
||||
import { MyComponent } from '{% static "js/components/myComponent.js" %}';
|
||||
new MyComponent();
|
||||
</script>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
2. **Не создавать встроенные `<style>` и `<script>` блоки**
|
||||
|
||||
3. **Использовать готовые компоненты**:
|
||||
- CSS: `common.css` классы
|
||||
- JS: `stringUtils`, `timing`, `scroll`, `listFilter`
|
||||
|
||||
4. **Если компонент переиспользуется** → выделить в `{app}/components/`
|
||||
|
||||
5. **Если стили > 50 строк** → создать отдельный CSS-файл
|
||||
|
||||
6. **Если скрипт > 100 строк** → создать отдельный JS-модуль
|
||||
|
||||
---
|
||||
|
||||
## Заключение
|
||||
|
||||
**Статус**: ✅ **ОСНОВНОЙ РЕФАКТОРИНГ ЗАВЕРШЁН**
|
||||
|
||||
Достигнуты все критические цели:
|
||||
- ✅ Устранено дублирование кода
|
||||
- ✅ Извлечены все встроенные стили
|
||||
- ✅ Извлечены критические JavaScript блоки
|
||||
- ✅ Декомпозированы самые большие файлы
|
||||
- ✅ Создана полная документация
|
||||
|
||||
**Проект готов к дальнейшей разработке** с улучшенной архитектурой шаблонов.
|
||||
|
||||
---
|
||||
|
||||
**Автор**: GitHub Copilot
|
||||
**Дата**: январь 2025
|
||||
**Версия**: 2.1
|
||||
@ -1,270 +0,0 @@
|
||||
# 🧪 Инструкция по тестированию системы уведомлений
|
||||
|
||||
**Дата:** 20 ноября 2025 г.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### 1. Запустить сервер
|
||||
```bash
|
||||
cd backend
|
||||
python manage.py runserver 9000
|
||||
```
|
||||
|
||||
### 2. Открыть приложение
|
||||
http://localhost:9000/
|
||||
|
||||
### 3. Войти в систему
|
||||
Используйте существующего пользователя
|
||||
|
||||
---
|
||||
|
||||
## ✅ Что тестировать
|
||||
|
||||
### ЭТАП 1-3: Автоматические уведомления (Signal Integration)
|
||||
|
||||
**1. Чат - Communications** 💬
|
||||
- Отправьте сообщение в чат → получатель получит уведомление
|
||||
- Упомяните @email в сообщении → уведомление об упоминании
|
||||
- Ответьте на сообщение → автор получит уведомление
|
||||
|
||||
**2. Заявки - Requests** 📋
|
||||
- Создайте заявку → руководители получат уведомление
|
||||
- Добавьте комментарий → автор получит уведомление
|
||||
- Одобрите/отклоните заявку → автор получит уведомление
|
||||
|
||||
**3. Документы - Documents** 📄
|
||||
- Загрузите документ → получатели получат уведомление
|
||||
- Ознакомьтесь с документом → автор получит уведомление (если все ознакомились)
|
||||
|
||||
**4. Календарь - Calendar** 📅
|
||||
- Создайте событие → участники получат уведомление
|
||||
- Измените событие → участники получат уведомление
|
||||
- Удалите событие → участники получат уведомление об отмене
|
||||
|
||||
**5. Лента - Feed** 📰
|
||||
- Создайте пост → пользователи получат уведомление (по типу)
|
||||
- Комментируйте пост → автор получит уведомление
|
||||
- Поставьте лайк → автор получит уведомление
|
||||
|
||||
### ЭТАП 4: UI и UX
|
||||
|
||||
**1. Колокольчик в Navbar** 🔔
|
||||
- Badge показывает количество непрочитанных
|
||||
- Клик открывает dropdown с последними 5 уведомлениями
|
||||
- Кнопка "Все" ведет на полный список
|
||||
- Кнопка "Настройки" ведет на настройки
|
||||
|
||||
**2. WebSocket Real-time** ⚡
|
||||
- Новое уведомление появляется мгновенно
|
||||
- Badge обновляется без перезагрузки
|
||||
- Список в dropdown обновляется
|
||||
|
||||
**3. Браузерные уведомления** 🖥️
|
||||
- При первом посещении запрашивается разрешение
|
||||
- Новые уведомления показываются в ОС
|
||||
- Клик по уведомлению переходит на объект
|
||||
- Уведомление отмечается прочитанным
|
||||
|
||||
**4. Звуковые уведомления** 🔊
|
||||
- При новом уведомлении звучит короткий звук
|
||||
- Переключатель в настройках: вкл/выкл
|
||||
- Настройка сохраняется в браузере
|
||||
|
||||
**5. Список уведомлений** 📋
|
||||
Откройте: `/notifications/`
|
||||
|
||||
- **Фильтр по категории:** выберите из 8 категорий
|
||||
- **Фильтр по статусу:** все/прочитанные/непрочитанные
|
||||
- **Поиск:** введите текст и нажмите "Найти"
|
||||
- **Пагинация:** переключение между страницами
|
||||
- **Действия:**
|
||||
- "Посмотреть" → переход к объекту
|
||||
- "Прочитано" → отметить прочитанным
|
||||
- Корзина → удалить уведомление
|
||||
- "Отметить все прочитанными" → массовое действие
|
||||
|
||||
**6. Настройки** ⚙️
|
||||
Откройте: `/notifications/settings/`
|
||||
|
||||
- **Звук:** переключатель (глобально)
|
||||
- **По категориям:** 8 карточек
|
||||
- Главный переключатель (вкл/выкл категорию)
|
||||
- Каналы: Веб (работает), Email/Telegram (скоро)
|
||||
- **Автосохранение:** изменения сразу отправляются на сервер
|
||||
- **Toast:** всплывает "Настройки сохранены"
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Генератор тестовых уведомлений
|
||||
|
||||
Для быстрого тестирования используйте скрипт:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
python generate_notification.py
|
||||
```
|
||||
|
||||
**Меню:**
|
||||
1. Новое сообщение в чате
|
||||
2. Новый документ
|
||||
3. Одобрена заявка
|
||||
4. Новое событие в календаре
|
||||
5. Новая публикация
|
||||
6. Системное уведомление
|
||||
7. Создать 5 разных уведомлений
|
||||
|
||||
После создания:
|
||||
- Проверьте колокольчик (должен обновиться badge)
|
||||
- Должно прийти браузерное уведомление
|
||||
- Должен воспроизвестись звук
|
||||
- Откройте список - уведомление должно быть там
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Что проверять
|
||||
|
||||
### Real-time (WebSocket)
|
||||
- [ ] Badge обновляется мгновенно
|
||||
- [ ] Список в dropdown обновляется
|
||||
- [ ] При отключении badge становится красным
|
||||
- [ ] При восстановлении badge возвращает цвет
|
||||
|
||||
### Браузерные уведомления
|
||||
- [ ] Запрос разрешения при первом визите
|
||||
- [ ] Уведомления показываются в ОС
|
||||
- [ ] Клик переходит на объект
|
||||
- [ ] Клик отмечает прочитанным
|
||||
- [ ] Автозакрытие через 10 сек (если не urgent)
|
||||
|
||||
### Звук
|
||||
- [ ] Воспроизводится при новом уведомлении
|
||||
- [ ] Переключатель в настройках работает
|
||||
- [ ] Настройка сохраняется в localStorage
|
||||
- [ ] Не воспроизводится если выключен
|
||||
|
||||
### Фильтрация
|
||||
- [ ] Фильтр по категории работает
|
||||
- [ ] Фильтр по статусу работает
|
||||
- [ ] Поиск находит по заголовку
|
||||
- [ ] Поиск находит по тексту
|
||||
- [ ] Фильтры можно комбинировать
|
||||
|
||||
### Пагинация
|
||||
- [ ] Показывает правильное количество страниц
|
||||
- [ ] Переход между страницами работает
|
||||
- [ ] Текущая страница подсвечена
|
||||
- [ ] Многоточие между далекими страницами
|
||||
- [ ] Прокрутка наверх при смене страницы
|
||||
|
||||
### Действия
|
||||
- [ ] "Прочитано" убирает синюю полосу
|
||||
- [ ] "Прочитано" уменьшает badge на 1
|
||||
- [ ] "Удалить" удаляет карточку
|
||||
- [ ] "Отметить все" обнуляет badge
|
||||
- [ ] "Посмотреть" переходит на URL
|
||||
|
||||
### Настройки
|
||||
- [ ] Переключатель категории включает/выключает
|
||||
- [ ] При выключении скрываются каналы
|
||||
- [ ] Изменения сохраняются на сервер
|
||||
- [ ] Toast показывает "Настройки сохранены"
|
||||
- [ ] Звук переключается глобально
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Известные ограничения
|
||||
|
||||
1. **Email/Telegram** - пока не работают (Этап 2 отложен)
|
||||
2. **Напоминания** - пока не работают (нужен Celery)
|
||||
3. **Группировка** - не реализована
|
||||
4. **Дайджесты** - не реализованы
|
||||
|
||||
---
|
||||
|
||||
## 📊 Ожидаемые результаты
|
||||
|
||||
### После создания уведомления:
|
||||
1. ✅ Badge показывает +1
|
||||
2. ✅ Dropdown обновляется (новое уведомление сверху)
|
||||
3. ✅ Браузерное уведомление всплывает
|
||||
4. ✅ Звук воспроизводится (если включен)
|
||||
5. ✅ Список обновляется (если открыт)
|
||||
|
||||
### После "Прочитано":
|
||||
1. ✅ Badge показывает -1
|
||||
2. ✅ Карточка теряет синюю полосу
|
||||
3. ✅ Opacity становится 70%
|
||||
4. ✅ Кнопка "Прочитано" исчезает
|
||||
|
||||
### После фильтрации:
|
||||
1. ✅ Показываются только подходящие уведомления
|
||||
2. ✅ Пагинация пересчитывается
|
||||
3. ✅ Счетчик total обновляется
|
||||
|
||||
### После изменения настроек:
|
||||
1. ✅ Toast "Настройки сохранены"
|
||||
2. ✅ Изменения применяются сразу
|
||||
3. ✅ Новые уведомления учитывают настройки
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Отладка
|
||||
|
||||
### Консоль браузера
|
||||
Откройте DevTools (F12) → Console
|
||||
|
||||
Смотрите логи:
|
||||
```
|
||||
[Notifications] WebSocket connected
|
||||
[Notifications] Received: {type: 'new_notification', ...}
|
||||
[Notifications] New notification: {...}
|
||||
[Notifications] Browser notification shown
|
||||
```
|
||||
|
||||
### Django logs
|
||||
Смотрите терминал где запущен runserver
|
||||
|
||||
### WebSocket
|
||||
Проверьте в DevTools → Network → WS:
|
||||
- Соединение установлено
|
||||
- Сообщения приходят
|
||||
- Нет ошибок
|
||||
|
||||
### API
|
||||
Проверьте запросы в DevTools → Network → Fetch/XHR:
|
||||
- `/api/notifications/` - список
|
||||
- `/api/notifications/count/` - счетчик
|
||||
- `/api/notifications/{id}/read/` - прочитать
|
||||
- `/api/notifications/settings/` - настройки
|
||||
|
||||
---
|
||||
|
||||
## ✅ Чеклист полного тестирования
|
||||
|
||||
- [ ] Создать уведомление через signal (отправить сообщение)
|
||||
- [ ] Создать уведомление через генератор
|
||||
- [ ] Проверить колокольчик обновился
|
||||
- [ ] Проверить браузерное уведомление
|
||||
- [ ] Проверить звук воспроизвелся
|
||||
- [ ] Открыть список уведомлений
|
||||
- [ ] Применить фильтр по категории
|
||||
- [ ] Применить фильтр по статусу
|
||||
- [ ] Выполнить поиск
|
||||
- [ ] Перейти на 2-ю страницу (если есть)
|
||||
- [ ] Отметить уведомление прочитанным
|
||||
- [ ] Удалить уведомление
|
||||
- [ ] Отметить все прочитанными
|
||||
- [ ] Открыть настройки
|
||||
- [ ] Выключить категорию
|
||||
- [ ] Включить категорию
|
||||
- [ ] Переключить звук
|
||||
- [ ] Проверить автосохранение
|
||||
|
||||
**Все работает? ПОЗДРАВЛЯЕМ! 🎉**
|
||||
|
||||
---
|
||||
|
||||
*Автор: GitHub Copilot + User*
|
||||
*Дата: 20 ноября 2025 г.*
|
||||
Loading…
x
Reference in New Issue
Block a user