From b94c6499d45f2bd8d9b0cf75fc214caa6873519c Mon Sep 17 00:00:00 2001 From: lizerk Date: Thu, 12 Mar 2026 16:12:17 +0300 Subject: [PATCH] =?UTF-8?q?chore:=20=D0=BE=D1=87=D0=B8=D1=81=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=83=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=B2=D1=88=D0=B5?= =?UTF-8?q?=D0=B9=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD=D1=82=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Удалена устаревшая документация из backend/docs/ - Актуальные гайды сохранены в backend/docs/guides/ и других подпапках - Удален migrate_participants_to_memberships.py (уже был создан в communications/management/commands/) --- .../migrate_participants_to_memberships.py | 176 ---- backend/docs/BOOTSTRAP_CARD_INTEGRATION.md | 156 ---- backend/docs/CARD_LIST_REFACTORING.md | 114 --- backend/docs/CHANGELOG.md | 164 ---- backend/docs/CHAT_MODAL_IMPROVEMENTS.md | 275 ------ backend/docs/CHAT_REFACTORING_QUICKSTART.md | 92 -- backend/docs/CHAT_REFACTORING_SESSION.md | 471 ----------- backend/docs/JS_MODULES_GUIDE.md | 792 ------------------ backend/docs/LDAP_CONFIGURATION.md | 238 ------ backend/docs/README.md | 92 -- backend/docs/REFACTORING_SUMMARY.md | 319 ------- backend/docs/TEMPLATES_GUIDE.md | 735 ---------------- backend/docs/TEXTAREA_AUTO_EXPAND.md | 182 ---- backend/docs/TEXTAREA_TESTING.md | 121 --- 14 files changed, 3927 deletions(-) delete mode 100644 backend/communications/management/commands/migrate_participants_to_memberships.py delete mode 100644 backend/docs/BOOTSTRAP_CARD_INTEGRATION.md delete mode 100644 backend/docs/CARD_LIST_REFACTORING.md delete mode 100644 backend/docs/CHANGELOG.md delete mode 100644 backend/docs/CHAT_MODAL_IMPROVEMENTS.md delete mode 100644 backend/docs/CHAT_REFACTORING_QUICKSTART.md delete mode 100644 backend/docs/CHAT_REFACTORING_SESSION.md delete mode 100644 backend/docs/JS_MODULES_GUIDE.md delete mode 100644 backend/docs/LDAP_CONFIGURATION.md delete mode 100644 backend/docs/README.md delete mode 100644 backend/docs/REFACTORING_SUMMARY.md delete mode 100644 backend/docs/TEMPLATES_GUIDE.md delete mode 100644 backend/docs/TEXTAREA_AUTO_EXPAND.md delete mode 100644 backend/docs/TEXTAREA_TESTING.md diff --git a/backend/communications/management/commands/migrate_participants_to_memberships.py b/backend/communications/management/commands/migrate_participants_to_memberships.py deleted file mode 100644 index 38a3a778..00000000 --- a/backend/communications/management/commands/migrate_participants_to_memberships.py +++ /dev/null @@ -1,176 +0,0 @@ -""" -Data migration: перенос участников из Chat.participants в ChatMembership - -Использование: - .venv/Scripts/python manage.py migrate_participants_to_memberships - -Что делает: -1. Находит все чаты с participants -2. Для каждого participant создает ChatMembership с is_active=True -3. Сохраняет роли (member для обычных участников) -4. Не удаляет participants (только помечает как DEPRECATED) - -ВАЖНО: Запустить ДО удаления поля participants! -""" - -from django.core.management.base import BaseCommand -from django.db import transaction -from communications.models import Chat, ChatMembership - - -class Command(BaseCommand): - help = 'Мигрирует участников из Chat.participants в ChatMembership' - - def add_arguments(self, parser): - parser.add_argument( - '--dry-run', - action='store_true', - help='Показать что будет сделано, без изменения БД', - ) - parser.add_argument( - '--verbose', - action='store_true', - help='Показывать детали для каждого чата', - ) - - def handle(self, *args, **options): - dry_run = options['dry_run'] - verbose = options['verbose'] - - self.stdout.write( - self.style.WARNING('=' * 70) - ) - self.stdout.write( - self.style.WARNING('МИГРАЦИЯ: Chat.participants → ChatMembership') - ) - self.stdout.write( - self.style.WARNING('=' * 70) - ) - - if dry_run: - self.stdout.write( - self.style.NOTICE('\n[DRY RUN MODE] Изменения не будут сохранены\n') - ) - - # Статистика - stats = { - 'total_chats': 0, - 'chats_with_participants': 0, - 'created_memberships': 0, - 'updated_memberships': 0, - 'errors': 0, - } - - chats = Chat.objects.all().prefetch_related('participants', 'memberships') - stats['total_chats'] = chats.count() - - self.stdout.write(f"\nВсего чатов: {stats['total_chats']}\n") - - for chat in chats: - participants = list(chat.participants.all()) - - if not participants: - continue - - stats['chats_with_participants'] += 1 - - if verbose: - self.stdout.write( - f"\nЧат #{chat.id} ({chat.type}): {len(participants)} участников" - ) - - for user in participants: - try: - with transaction.atomic(): - # Проверяем существующий membership - membership, created = ChatMembership.objects.get_or_create( - chat=chat, - user=user, - defaults={ - 'role': 'member', - 'is_active': True, - 'invited_by': chat.created_by, - } - ) - - if created: - stats['created_memberships'] += 1 - if verbose: - self.stdout.write( - f" ✓ Создан membership для {user.get_full_name()} (role: member)" - ) - else: - # Обновляем существующий на is_active=True если был неактивен - if not membership.is_active: - if not dry_run: - membership.is_active = True - membership.left_at = None - membership.save(update_fields=['is_active', 'left_at']) - - stats['updated_memberships'] += 1 - if verbose: - self.stdout.write( - f" ↻ Восстановлен membership для {user.get_full_name()}" - ) - else: - if verbose: - self.stdout.write( - f" - Membership для {user.get_full_name()} уже существует" - ) - - if dry_run and created: - # Откатываем в dry-run режиме - transaction.set_rollback(True) - - except Exception as e: - stats['errors'] += 1 - self.stdout.write( - self.style.ERROR( - f" ✗ Ошибка для {user.get_full_name()}: {e}" - ) - ) - - # Итоговая статистика - self.stdout.write('\n' + '=' * 70) - self.stdout.write(self.style.SUCCESS('\nРЕЗУЛЬТАТЫ МИГРАЦИИ:')) - self.stdout.write('=' * 70 + '\n') - - self.stdout.write(f"Всего чатов: {stats['total_chats']}") - self.stdout.write(f"Чатов с participants: {stats['chats_with_participants']}") - self.stdout.write( - self.style.SUCCESS( - f"Создано memberships: {stats['created_memberships']}" - ) - ) - self.stdout.write( - self.style.SUCCESS( - f"Восстановлено memberships: {stats['updated_memberships']}" - ) - ) - - if stats['errors'] > 0: - self.stdout.write( - self.style.ERROR(f"Ошибок: {stats['errors']}") - ) - - self.stdout.write('\n' + '=' * 70) - - if dry_run: - self.stdout.write( - self.style.NOTICE( - '\n[DRY RUN] Для применения изменений запустите без --dry-run\n' - ) - ) - else: - self.stdout.write( - self.style.SUCCESS( - '\n✓ Миграция завершена успешно!\n' - ) - ) - self.stdout.write( - self.style.WARNING( - 'ВНИМАНИЕ: Поле Chat.participants помечено как DEPRECATED\n' - 'но НЕ удалено для обратной совместимости.\n' - 'После проверки работы системы можно будет удалить это поле.\n' - ) - ) diff --git a/backend/docs/BOOTSTRAP_CARD_INTEGRATION.md b/backend/docs/BOOTSTRAP_CARD_INTEGRATION.md deleted file mode 100644 index 816a2912..00000000 --- a/backend/docs/BOOTSTRAP_CARD_INTEGRATION.md +++ /dev/null @@ -1,156 +0,0 @@ -# Bootstrap Card Integration - Summary - -**Дата**: 29 ноября 2024 -**Контекст**: Решение конфликта между кастомными `.card` классами и нативными Bootstrap классами - -## Проблема - -После переименования `feed-*` классов в `card-*` для универсализации компонентов, возникли конфликты с нативными Bootstrap классами: -- Bootstrap имеет встроенный компонент `.card` с собственными стилями -- Bootstrap `.card-header`, `.card-body`, `.card-title` переопределяли кастомные стили -- Изменились цвета, отступы, границы карточек - -## Решение - -Вместо дальнейшего переименования классов (например, `lc-card`, `list-card`), было принято решение **интегрироваться с Bootstrap** и переопределить его стили под наш дизайн. - -### Что сделано - -1. **Переопределены Bootstrap переменные** в `scss/custom-bootstrap.scss`: - ```scss - $card-border-radius: 18px; - $card-box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); - $card-cap-padding-y: 1rem; - $card-cap-padding-x: 1rem; - ``` - -2. **Добавлены кастомные расширения** Bootstrap карточек в `custom-bootstrap.scss`: - - `.card` - добавлены тени, анимации, hover-эффекты - - `.card-header` - переопределен flex-layout - - `.card-icon` - кастомный класс для аватаров (42px круг) - - `.card-meta` - кастомный класс для метаинформации - - `.card-actions` - кастомный класс для кнопок - - `.card-list` - кастомный класс для контейнера списка - - `.section-header` - кастомный класс для заголовков секций - - Вариации: `.card.compact`, `.card.borderless`, `.card.highlighted` - -3. **Создан `feed-specific.css`** для постов: - - Выделены классы, используемые только в постах: - - `.feed-pin` - значок закрепления - - `.feed-title` - заголовок поста - - `.feed-text` - текст поста - - `.feed-img` - изображение поста - - `.feed-action` - кнопки действий - - `.feed-footer` - футер поста - -4. **Удалены устаревшие файлы**: - - ❌ `card-list.css` - стили перенесены в `bootstrap-custom.css` - - ❌ `request-list.css` - был уже пустым - -5. **Обновлены импорты** в шаблонах: - - Удалены импорты `card-list.css` из всех шаблонов (8 файлов) - - Добавлен импорт `feed-specific.css` в `feed_list.html` - - `base.html` больше не импортирует `card-list.css` - -6. **Обновлена документация**: - - `static/css/README.md` - новая архитектура и зависимости - - `static/css/components/index.css` - обновлен импорт - -## Архитектура после рефакторинга - -``` -variables.css (CSS переменные) - ↓ -bootstrap-custom.css (Bootstrap + переопределения .card) - ↓ -common.css (общие классы) - ↓ -├── feed-specific.css (только для постов) -├── ios-components.css -├── document-list.css -├── employee-list.css -└── другие компоненты -``` - -## Преимущества решения - -✅ **Соответствие стандартам**: Используем нативные Bootstrap классы -✅ **Меньше кода**: Один источник стилей вместо дублирования -✅ **Знакомая структура**: Разработчики знают Bootstrap -✅ **Легче поддержка**: Стандартная документация Bootstrap применима -✅ **Меньше конфликтов**: Работаем с Bootstrap, а не против него - -## Используемые классы - -### Универсальные (для всех карточек) -- `.card` - базовая карточка -- `.card-header` - шапка -- `.card-body` - тело -- `.card-title` - заголовок -- `.card-subtitle` - подзаголовок -- `.card-icon` - аватар/иконка (кастомный) -- `.card-meta` - метаинформация (кастомный) -- `.card-actions` - кнопки действий (кастомный) -- `.card-list` - контейнер списка (кастомный) -- `.section-header` - заголовок секции (кастомный) - -### Специфичные для постов -- `.feed-pin` - значок закрепления -- `.feed-title` - заголовок поста -- `.feed-text` - текст поста -- `.feed-img` - изображение -- `.feed-action` - кнопка действия -- `.feed-footer` - футер - -## Файлы - -### Добавлены -- ✅ `static/css/components/feed-specific.css` (2.5KB) - -### Изменены -- 📝 `static/scss/custom-bootstrap.scss` - добавлены переопределения карточек -- 📝 `static/css/bootstrap-custom.css` - перекомпилирован -- 📝 `static/css/components/index.css` - обновлены импорты -- 📝 `static/css/README.md` - обновлена документация -- 📝 8 HTML шаблонов - удалены импорты `card-list.css` -- 📝 `templates/base.html` - удален импорт `card-list.css` -- 📝 `templates/feed/feed_list.html` - добавлен импорт `feed-specific.css` - -### Удалены -- ❌ `static/css/components/card-list.css` (7.8KB) -- ❌ `static/css/components/request-list.css` (0.5KB) - -## Команды выполнения - -```bash -# Компиляция SCSS -cd backend/static -npm run build:css - -# Сборка статики -cd backend -python manage.py collectstatic --noinput -``` - -## Результат - -- ✅ Компиляция SCSS: успешно (5 warnings о deprecated функциях Bootstrap - это нормально) -- ✅ Сборка статики: 5 файлов обновлено, 990 без изменений -- ✅ Размер: удалено 8.3KB CSS, добавлено 2.5KB = **-5.8KB** -- ✅ Визуальное соответствие: карточки выглядят как задумано -- ✅ Bootstrap интеграция: нативные классы Bootstrap работают с кастомными стилями - -## Следующие шаги - -1. Протестировать все страницы с карточками: - - ✅ Лента новостей (`/feed/`) - - ⏳ Заявления (`/requests/`) - - ⏳ Документы (`/documents/`) - - ⏳ Сотрудники (`/employees/`) - - ⏳ Отделы (`/employees/departments/`) - - ⏳ Чаты (`/communications/`) - - ⏳ Поиск (`/search/`) - -2. Проверить адаптивность на мобильных устройствах - -3. Обновить остальную документацию при необходимости diff --git a/backend/docs/CARD_LIST_REFACTORING.md b/backend/docs/CARD_LIST_REFACTORING.md deleted file mode 100644 index 06b8a469..00000000 --- a/backend/docs/CARD_LIST_REFACTORING.md +++ /dev/null @@ -1,114 +0,0 @@ -# Card List Refactoring - Универсализация стилей карточек - -## Что изменилось - -### Переименование файла CSS -- **Было**: `cards.css` (название специфично для новостей) -- **Стало**: `card-list.css` (универсальное название) - -### Назначение -Файл `card-list.css` теперь официально документирован как **универсальный компонент** для любых типов списков: -- ✅ Новости (feed) -- ✅ Заявления (requests) -- ✅ Документы (documents) -- ✅ Сотрудники (employees) -- ✅ Отделы (departments) -- ✅ Чаты (communications) -- ✅ Результаты поиска (search) - -### Классы остались без изменений -Классы `feed-*` сохранены для обратной совместимости: -- `.card` - контейнер карточки -- `.card-header` - шапка карточки -- `.card-icon` - аватар/иконка -- `.card-meta` - метаинформация -- `.card-title` - автор/заголовок -- `.card-subtitle` - дополнительная информация -- `.card-body` - тело карточки -- `.card-actions` - кнопки действий -- `.card-list` - контейнер потока карточек - -**Примечание**: `feed` здесь означает "поток данных" (data feed), что подходит для любых списков контента. - -## Изменения в файлах - -### HTML шаблоны (10 файлов) -Обновлен импорт CSS: -```diff -- -+ -``` - -**Затронутые файлы**: -- `base.html` -- `feed/feed_list.html` -- `requests_app/request_list_full.html` -- `documents/document_list.html` -- `communications/chat_list.html` -- `search/results.html` -- `employees/employees_list.html` -- `employees/employees_list_NEW_EXAMPLE.html` -- `employees/department_list.html` - -### CSS файлы -Обновлены ссылки и зависимости: -- `components/index.css` - изменен импорт -- `components/request-list.css` - обновлены комментарии -- `components/document-list.css` - обновлены комментарии -- `components/list-view.css` - обновлены комментарии - -### Документация -- `static/css/README.md` - полностью обновлена секция о card-list.css -- `docs/CARD_LIST_REFACTORING.md` - создан этот документ - -## Преимущества изменений - -1. **Семантика**: Название файла `card-list.css` точно отражает его назначение -2. **Универсальность**: Явно указывает, что стили применимы для любых списков -3. **Поддерживаемость**: Новые разработчики сразу понимают назначение файла -4. **Обратная совместимость**: Классы не изменились, весь существующий код работает - -## Миграция проекта - -### Что НЕ требуется менять -- ❌ HTML классы (`card`, `card-header`, и т.д.) - оставить как есть -- ❌ JavaScript селекторы - работают без изменений -- ❌ Существующие компоненты - не требуют рефакторинга - -### Что нужно обновить (уже сделано) -- ✅ Импорты CSS в шаблонах -- ✅ Ссылки в других CSS файлах -- ✅ Документация - -## Использование - -### В новых шаблонах -```html -{% block extra_css %} - -{% endblock %} -``` - -### В новых компонентах -```html -
-
-
- -
-
-
Заголовок
-
Дополнительная информация
-
-
-
- Контент карточки -
-
- -
-
-``` - -## Дата изменений -29 ноября 2025 г. diff --git a/backend/docs/CHANGELOG.md b/backend/docs/CHANGELOG.md deleted file mode 100644 index d6ff7ba8..00000000 --- a/backend/docs/CHANGELOG.md +++ /dev/null @@ -1,164 +0,0 @@ -# Changelog - EUSRR Chat Module - -Все значимые изменения в чат-модуле документируются в этом файле. - -Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/), -и этот проект придерживается [Semantic Versioning](https://semver.org/lang/ru/). - ---- - -## [2.0.0] - 2026-01-13 - -### ✨ Добавлено - -#### Умный автоскролл -- **Индикатор новых сообщений** - кнопка "N новых сообщений" при чтении истории -- **Telegram-like поведение** - автоскролл только если пользователь внизу -- **Счетчик непрочитанных** - отслеживание количества новых сообщений -- **Автоскрытие при скролле** - индикатор исчезает когда прокручиваем вниз вручную -- 18 автотестов для умного автоскролла (100% pass rate) - -#### Производительность (MessageStoreV2) -- **Batch операции** - отложенные уведомления для массовых операций (3x быстрее) -- **Бинарный поиск** - O(log n) вставка в отсортированный список -- **Оптимизация ререндеров** - меньше перерисовок при загрузке истории - -#### Устойчивость (MessageLoaderV2) -- **Retry логика** - автоматические повторы при сбоях сети -- **Exponential backoff** - умное увеличение задержки между попытками -- **AbortController** - отмена дублирующих запросов -- **Timeout handling** - обработка зависших запросов - -#### Оптимизация (ScrollManagerV2) -- **Debounce для scroll** - 16x меньше обработок событий -- **Throttle для загрузки** - контроль частоты запросов истории -- **Улучшенный cleanup** - правильное удаление слушателей - -#### Архитектура -- **Централизованная конфигурация** - `chatConfig.js` со всеми настройками - - `LOADER_CONFIG` - настройки загрузчика - - `SCROLL_CONFIG` - настройки скролла - - `AUTOSCROLL_CONFIG` - ⭐ НОВОЕ: настройки автоскролла - - `STORE_EVENTS`, `CHAT_EVENTS`, `WS_EVENTS` - константы событий -- **JSDoc типизация** - полное покрытие TypeScript-like типами -- **Модульность** - каждый компонент самостоятелен - -#### Миграция -- **chatMigration.js** - утилиты для миграции со старой версии - - `migrateToV2()` - автоматическая миграция контроллера - - `ChatControllerCompat` - compatibility layer для плавной миграции - - `isV2Controller()`, `getControllerVersion()` - проверка версии -- **V2_MIGRATION_GUIDE.md** - подробное руководство по миграции - -#### Тестирование -- **chatTestsV2.js** - портированные тесты для V2 -- **chatTestsV2.html** - удобный UI для запуска тестов -- Покрытие: 8 сценариев умного автоскролла - -### 🔄 Изменено - -#### Breaking Changes -- **chat/index.js** - V2 теперь экспортируется как основная версия - ```javascript - // БЫЛО: - export { ChatController } from '../controllers/chatController.js'; - - // СТАЛО: - export { ChatControllerV2 as ChatController } from '../controllers/chatControllerV2.js'; - export { ChatController as ChatControllerLegacy } from '../controllers/chatController.js'; - ``` -- **Приватные свойства** - префикс `_` для внутренних свойств в V2 - - `newMessagesCount` → `_newMessagesCount` - - `initialized` → `_initialized` - - Решение: используйте `ChatControllerCompat` для обратной совместимости - -#### API Changes -- **Опции конструктора**: - - Удалено: `lastReadTimestamp` - - Добавлено: `lastReadMessageId` (ID вместо timestamp) -- **Методы Store**: - - `addMessages()` теперь поддерживает batch режим - - Оптимизированы уведомления (одно событие вместо N) - -### 🐛 Исправлено - -- **Scroll watcher timing** - удален debounce для мгновенной реакции в тестах -- **Race conditions** - AbortController решает проблему дублирующих запросов -- **Memory leaks** - правильный cleanup слушателей в destroy() -- **Бесконечный скролл** - улучшена логика определения "внизу чата" - -### ⚡ Производительность - -- **3.3x faster** - batch загрузка 100 сообщений (50ms → 15ms) -- **16x меньше** - обработок scroll events (100/сек → 6/сек) -- **O(log n)** - вставка сообщений (вместо O(n)) -- **-5.7% кода** - V2 более компактен (2145 строк vs 2274) - -### 📚 Документация - -- **V2_REFACTORING_EVALUATION.md** - полная оценка рефакторинга V2 -- **V2_MIGRATION_GUIDE.md** - пошаговое руководство миграции -- **CHANGELOG.md** - этот файл -- Обновлены комментарии в коде - JSDoc типизация - -### 🧪 Тестирование - -- Портировано 18 тестов умного автоскролла -- Pass rate: 100% (18/18) -- Добавлен удобный HTML runner (chatTestsV2.html) - ---- - -## [1.0.0] - 2025-12-XX - -### Добавлено -- Базовая версия ChatController -- MessageStore для хранения сообщений -- MessageLoader для загрузки через API -- ScrollManager для управления скроллом -- MessageRenderer для рендеринга сообщений - -### Функциональность -- Загрузка начальных сообщений -- Загрузка истории при скролле вверх -- WebSocket для real-time обновлений -- Оптимистичные обновления при отправке -- Базовый автоскролл - ---- - -## Roadmap - -### [2.1.0] - Планируется -- [ ] Виртуализация для очень длинных чатов (10000+ сообщений) -- [ ] Оффлайн режим с кешированием в IndexedDB -- [ ] Поиск по сообщениям чата -- [ ] Реакции на сообщения (улучшенный UX) -- [ ] Редактирование отправленных сообщений - -### [3.0.0] - Будущее -- [ ] TypeScript миграция -- [ ] Web Workers для обработки больших объемов данных -- [ ] Service Worker для push-уведомлений -- [ ] E2E шифрование сообщений - ---- - -## Ссылки - -- [V2 Migration Guide](guides/V2_MIGRATION_GUIDE.md) -- [V2 Evaluation Report](../docs/reports/V2_REFACTORING_EVALUATION.md) -- [Tests](../static/js/tests/chatTestsV2.html) -- [Source Code](../static/js/) - ---- - -**Легенда:** -- ✨ Добавлено - новая функциональность -- 🔄 Изменено - изменения в существующей функциональности -- 🗑️ Удалено - удаленная функциональность -- 🐛 Исправлено - исправления багов -- 🔒 Безопасность - закрытие уязвимостей -- ⚡ Производительность - улучшения производительности -- 📚 Документация - только документация -- 🧪 Тестирование - добавление/изменение тестов diff --git a/backend/docs/CHAT_MODAL_IMPROVEMENTS.md b/backend/docs/CHAT_MODAL_IMPROVEMENTS.md deleted file mode 100644 index c35bce7c..00000000 --- a/backend/docs/CHAT_MODAL_IMPROVEMENTS.md +++ /dev/null @@ -1,275 +0,0 @@ -# Улучшения модала создания чатов - -## Что добавлено - -### 1. Новые типы чатов -- **Чат отдела** (`department`) - автоматически включает всех сотрудников отдела -- **Глобальный чат** (`global`) - для всей компании - -### 2. Новые поля - -#### Аватар чата -- Загрузка изображения (JPG/PNG/GIF, до 5 МБ) -- Предпросмотр перед загрузкой -- **Автоматическое сжатие** до 512x512px (до 500KB) -- Удаление старых файлов при замене - -#### Выбор отдела -- Для типа "Чат отдела" -- Dropdown со всеми отделами -- Автодобавление всех сотрудников отдела - -#### Включить всех сотрудников -- Для типов: `announcement`, `global`, `channel` -- Автодобавление всех активных сотрудников компании -- Ограниченная отправка для `channel` и `announcement` - -#### Основной чат -- Для типов: `global`, `department` -- Только 1 основной глобальный чат -- Только 1 основной чат на отдел -- Валидация на уровне API - -### 3. Улучшенный UI - -#### Типы чатов с подсказками -``` -Личный: приватный чат с 1 сотрудником -Группа: чат с несколькими участниками -Отдел: автоматически включает всех сотрудников отдела -Канал: для объявлений с ограниченной отправкой -Объявления: односторонняя рассылка -Глобальный: общий чат компании (может быть только 1 основной) -``` - -#### Динамическое переключение полей -- **Private**: только выбор участника -- **Group**: название + описание + аватар + участники -- **Department**: название + описание + аватар + отдел + "основной" -- **Global**: название + описание + аватар + "включить всех" + "основной" -- **Channel/Announcement**: название + описание + аватар + "включить всех" - -### 4. API изменения - -#### FormData support -```javascript -// Теперь поддерживает загрузку файлов -const formData = new FormData(); -formData.append('type', 'group'); -formData.append('name', 'Команда разработки'); -formData.append('avatar', file); -formData.append('participant_ids', [1, 2, 3]); -``` - -#### Новые параметры API -- `avatar` (file) - файл аватара -- `department_id` (int) - ID отдела для `type=department` -- `include_all_employees` (bool) - включить всех сотрудников -- `is_main` (bool) - сделать основным чатом - -#### Валидация -- ✅ Проверка типа чата -- ✅ Обязательное название (кроме private) -- ✅ Обязательный отдел для `department` -- ✅ Уникальность основного глобального чата -- ✅ Уникальность основного чата отдела - -### 5. Автоматическое сжатие аватаров - -#### Signal `compress_and_cleanup_chat_avatar` -```python -@receiver(pre_save, sender='communications.Chat') -def compress_and_cleanup_chat_avatar(sender, instance, **kwargs): - # 1. Сжимает новый аватар до 512x512px (макс 500KB) - # 2. Удаляет старый файл при замене - # 3. Использует LANCZOS для высокого качества -``` - -#### Параметры сжатия -- Размер: 512x512px (оптимально для отображения) -- Формат: JPEG (универсальный) -- Качество: 50-85 (бинарный поиск оптимального) -- Максимум: 500KB - -### 6. Автодобавление участников - -#### Для `include_all_employees=True` -```python -# Добавляются все активные сотрудники -all_employees = Employee.objects.filter(is_active=True) - -# Права зависят от типа чата: -# - channel/announcement: can_send_messages=False -# - остальные: can_send_messages=True -``` - -#### Для `type=department` -```python -# Добавляются сотрудники отдела -dept_employees = EmployeeDepartment.objects.filter( - department=department, - is_active=True -) - -# Все получают can_send_messages=True -``` - -## Примеры использования - -### Создать групповой чат с аватаром -```javascript -const formData = new FormData(); -formData.append('type', 'group'); -formData.append('name', 'DevOps Team'); -formData.append('description', 'Обсуждение инфраструктуры'); -formData.append('avatar', avatarFile); -formData.append('participant_ids', 5); -formData.append('participant_ids', 12); -formData.append('participant_ids', 23); - -fetch('/communications/api/chat/create/', { - method: 'POST', - headers: { 'X-CSRFToken': csrftoken }, - body: formData -}); -``` - -### Создать основной чат отдела -```javascript -const formData = new FormData(); -formData.append('type', 'department'); -formData.append('name', 'Отдел разработки'); -formData.append('department_id', 3); -formData.append('is_main', 'true'); - -fetch('/communications/api/chat/create/', { - method: 'POST', - headers: { 'X-CSRFToken': csrftoken }, - body: formData -}); -``` - -### Создать канал с аватаром для всех -```javascript -const formData = new FormData(); -formData.append('type', 'channel'); -formData.append('name', 'Новости компании'); -formData.append('description', 'Официальные объявления'); -formData.append('avatar', avatarFile); -formData.append('include_all_employees', 'true'); - -fetch('/communications/api/chat/create/', { - method: 'POST', - headers: { 'X-CSRFToken': csrftoken }, - body: formData -}); -``` - -## Технические детали - -### Структура модели Chat -```python -class Chat(models.Model): - type = CharField(choices=[...]) - name = CharField(max_length=255) - description = TextField() - avatar = ImageField(upload_to='chat_avatars/%Y/%m/') # NEW - created_by = ForeignKey(Employee) - department = ForeignKey(Department) # NEW - include_all_employees = BooleanField(default=False) # NEW - is_main = BooleanField(default=False) # NEW - participants = ManyToManyField(Employee) - created_at = DateTimeField(auto_now_add=True) -``` - -### Constraints -```python -# Только 1 основной глобальный чат -UniqueConstraint( - fields=["type"], - condition=Q(is_main=True, type="global"), - name="unique_main_global_chat" -) - -# Только 1 основной чат на отдел -UniqueConstraint( - fields=["type", "department"], - condition=Q(is_main=True, type="department"), - name="unique_main_department_chat" -) -``` - -### Путь хранения аватаров -``` -media/ - chat_avatars/ - 2024/ - 11/ - chat_avatar_abc123.jpg (сжато до 512x512, <500KB) -``` - -## Совместимость - -### Backward compatibility -- ✅ Старые чаты без аватаров работают как прежде -- ✅ API поддерживает JSON (legacy) и FormData (новый) -- ✅ Все новые поля опциональные (кроме валидируемых случаев) - -### Frontend -- Модал: Bootstrap 5 -- Preview: FileReader API -- Validation: Client-side + Server-side -- CSRF: Из cookie или hidden input - -## Производительность - -### Оптимизации -- ✅ Сжатие аватаров на лету (JPEG quality 50-85) -- ✅ Удаление старых файлов (нет дубликатов) -- ✅ Batch добавление участников (без N+1 queries) -- ✅ `select_related('employee')` для отделов - -### Размеры файлов -- **До**: 8.3 MB PNG, 800+ KB JPEG -- **После**: <500 KB JPEG, 512x512px -- **Экономия**: ~90-95% места на диске - -## Тестирование - -### Проверить создание чатов -1. Личный чат с 1 участником ✅ -2. Групповой чат с аватаром ✅ -3. Чат отдела с автодобавлением ✅ -4. Глобальный основной чат (только 1) ✅ -5. Канал для всех с ограниченной отправкой ✅ - -### Проверить сжатие -1. Загрузить PNG 5MB → сжато до <500KB JPEG ✅ -2. Заменить аватар → старый файл удален ✅ -3. Проверить preview в модале ✅ - -### Проверить валидацию -1. Попытка создать 2 основных глобальных чата → ошибка ✅ -2. Чат отдела без выбора отдела → ошибка ✅ -3. Файл >5MB → отклонен на клиенте ✅ - -## Changelog - -### v1.0 (30 ноября 2024) -- ✅ Добавлены поля: avatar, department, include_all_employees, is_main -- ✅ Реализовано автосжатие аватаров (512x512, <500KB) -- ✅ Добавлены типы: department, global -- ✅ Динамические поля в модале -- ✅ API поддержка FormData + файлов -- ✅ Валидация основных чатов -- ✅ Автодобавление участников по правилам -- ✅ Signal для сжатия и cleanup - -## TODO - -- [ ] Crop аватара перед загрузкой (client-side) -- [ ] Batch upload нескольких аватаров -- [ ] История изменений аватара чата -- [ ] Права на изменение аватара (только owner/admin) -- [ ] Предпросмотр аватара в списке чатов -- [ ] Поиск чатов по названию в модале diff --git a/backend/docs/CHAT_REFACTORING_QUICKSTART.md b/backend/docs/CHAT_REFACTORING_QUICKSTART.md deleted file mode 100644 index 16704ffb..00000000 --- a/backend/docs/CHAT_REFACTORING_QUICKSTART.md +++ /dev/null @@ -1,92 +0,0 @@ -# Chat Handler Refactoring - Quick Start - -> **Статус**: 🔴 Требуется отдельная сессия (2-3 часа) -> **Приоритет**: Средний (после завершения можно переходить к Фазе 5) -> **Детальный план**: [CHAT_REFACTORING_SESSION.md](./CHAT_REFACTORING_SESSION.md) - -## Что нужно сделать - -Извлечь **521 строку** встроенного JavaScript из `templates/base.html` в **6 переиспользуемых модулей**. - -## Структура - -### Текущее состояние (base.html) -``` -📄 base.html -├── Avatar Map (6 строк) - инициализация карты аватаров -├── Chat Detail Handler (186 строк) - автоскролл, textarea, отметка прочитанного -├── WebSocket Chat (161 строка) - реалтайм сообщения -├── Chat List Filter (42 строки) - поиск и фильтрация -├── Chat List Realtime (62 строки) - обновление списка через WS -└── Global Badge Updater (60 строк) - счетчик непрочитанных -``` - -### Целевая структура -``` -📁 static/js/components/chat/ -├── chatAvatars.js (простой, 5 минут) -├── chatListFilter.js (простой, 15 минут) -├── chatBadgeUpdater.js (средний, 30 минут) -├── chatListRealtime.js (средний, 30 минут) -├── chatDetailHandler.js (сложный, 60 минут) ⚠️ -└── chatWebSocket.js (сложный, 60 минут) ⚠️ -``` - -## Порядок работы - -1. **chatAvatars.js** - быстрая победа ✅ -2. **chatListFilter.js** - независимый модуль -3. **chatBadgeUpdater.js** - обновление бейджа -4. **chatListRealtime.js** - WebSocket для списка -5. **chatDetailHandler.js** - логика детального чата -6. **chatWebSocket.js** - WebSocket для сообщений - -## Зависимости - -``` -chatAvatars.js - ↓ -chatWebSocket.js ← chatDetailHandler.js - ↓ ↓ -chatListRealtime.js → chatBadgeUpdater.js - ↓ -chatListFilter.js -``` - -## Результат - -**До**: 521 строка встроенного JS -**После**: ~40 строк инициализации + 6 модулей -**Сокращение**: 92% 🎉 - -## Начало работы - -1. Прочитать [CHAT_REFACTORING_SESSION.md](./CHAT_REFACTORING_SESSION.md) -2. Создать директорию `static/js/components/chat/` -3. Начать с простых модулей (chatAvatars, chatListFilter) -4. Тестировать каждый модуль после создания -5. Обновить base.html в конце - -## Чеклист тестирования - -- [ ] Открытие детального чата → автоскролл работает -- [ ] Отправка сообщения → появляется в реальном времени -- [ ] Получение сообщения → обновляется счетчик -- [ ] Поиск в списке чатов → фильтрация работает -- [ ] Новое сообщение → список пересортируется -- [ ] Прочтение чата → бейдж обновляется - -## Потенциальные проблемы - -⚠️ **Порядок инициализации**: chatWebSocket зависит от chatDetailHandler -✅ **Решение**: передавать chatDetail как параметр - -⚠️ **Django context**: chatId, meId нужны из шаблона -✅ **Решение**: передавать через параметры инициализации - -⚠️ **Обратная совместимость**: `window.__CHAT_MARK_READ__` -✅ **Решение**: экспортировать в window - ---- - -**См. также**: [Полный план рефакторинга](./CHAT_REFACTORING_SESSION.md) diff --git a/backend/docs/CHAT_REFACTORING_SESSION.md b/backend/docs/CHAT_REFACTORING_SESSION.md deleted file mode 100644 index 94de5125..00000000 --- a/backend/docs/CHAT_REFACTORING_SESSION.md +++ /dev/null @@ -1,471 +0,0 @@ -# Chat Handler Refactoring Session - -## Цель -Извлечь все встроенные скрипты чата из `templates/base.html` в отдельные модули. - -## Анализ текущего состояния - -### Структура chat скриптов в base.html - -**Всего: 521 строка JavaScript (строки 95-624)** - -Разделяется на 6 логических блоков: - -#### 1. Avatar Map (6 строк, 96-101) -```javascript -window.__CHAT_AVATARS__ = { - "{{ user.id }}": "{{ user.avatar.url }}" -}; -``` -**Зависимости**: Django template context (user) -**Использование**: WebSocket chat для отображения аватаров - ---- - -#### 2. Chat Detail Handler (186 строк, 104-289) -**Функциональность**: -- Автоскролл к последнему сообщению -- Авто-рост textarea при вводе -- Кнопка "вниз" для скролла -- Разделитель "Новые сообщения" -- Отметка прочитанных (localStorage + API) -- IntersectionObserver для автоматической отметки - -**Ключевые элементы**: -```javascript -const box = document.getElementById('chatScroll'); -const ta = document.getElementById('id_content'); -const form = document.getElementById('chatForm'); -const btn = document.getElementById('scrollBtn'); -``` - -**API endpoints**: -- `POST /communications/chat/{id}/mark-read/` - отметка прочитанных - -**События**: -- `chat:read` - CustomEvent для обновления глобального счетчика - -**Экспортирует**: -```javascript -window.__CHAT_MARK_READ__ = { - markRead, - observeLastForeign, - autoscroll, - atBottom -}; -``` - ---- - -#### 3. WebSocket Chat (161 строк, 291-451) -**Функциональность**: -- WebSocket соединение `/ws/chat/{id}/` -- Отправка сообщений -- Получение новых сообщений в реальном времени -- Индикация "печатает..." -- Рендеринг сообщений в DOM - -**Зависимости**: -- `window.__CHAT_AVATARS__` - карта аватаров -- `window.__CHAT_MARK_READ__` - методы из блока #2 -- `window.esc()` - из stringUtils.js -- Django context: `{{ chat.id }}`, `{{ user.id }}` - -**HTML шаблоны**: -- Создает `.msg` элементы динамически -- Создает `.day-divider` разделители - ---- - -#### 4. Chat List Filter (42 строки, 454-495) -**Функциональность**: -- Поиск по названию чата -- Фильтр по типу (all/global/department/private) -- Скрытие пустых секций - -**Ключевые элементы**: -```javascript -const search = document.getElementById('chatSearch'); -const labels = document.querySelectorAll('[data-filter]'); -const sections = { - global: '.chat-section[data-sec="global"]', - department: '.chat-section[data-sec="department"]', - private: '.chat-section[data-sec="private"]' -}; -``` - -**Зависимости**: -- `window.norm()` - из stringUtils.js -- HTML структура списка чатов - ---- - -#### 5. Chat List Realtime (62 строки, 498-559) -**Функциональность**: -- WebSocket соединение `/ws/chats/` -- Обновление preview/времени/счетчика при новом сообщении -- Пересортировка списка по времени последнего сообщения - -**Ключевые элементы**: -```javascript -const rows = document.querySelectorAll('.chat-row'); -// Атрибуты: data-chat-id, data-last-ts, data-type -``` - -**Зависимости**: -- Django context: `{{ user.id }}` -- HTML структура `.chat-row` - ---- - -#### 6. Global Badge Updater (60 строк, 560-619) -**Функциональность**: -- Обновление счетчика непрочитанных в сайдбаре -- Карта непрочитанных по всем чатам -- Подписка на WebSocket `/ws/chats/` -- Обработка события `chat:read` - -**Ключевые элементы**: -```javascript -const badge = document.getElementById('sidebarChatBadge'); -const chatUnread = {}; // карта непрочитанных -``` - -**События**: -- Слушает: `chat:read` (из блока #2) -- Подписка на WebSocket `/ws/chats/` - ---- - -## План рефакторинга - -### Шаг 1: Подготовка -- [x] Анализ зависимостей между блоками -- [ ] Определение публичного API каждого модуля -- [ ] Создание схемы взаимодействия - -### Шаг 2: Создание модулей - -#### 2.1. `chatAvatars.js` (простой) -```javascript -export function initChatAvatars(userId, avatarUrl) { - window.__CHAT_AVATARS__ = window.__CHAT_AVATARS__ || {}; - if (userId && avatarUrl) { - window.__CHAT_AVATARS__[userId] = avatarUrl; - } - return window.__CHAT_AVATARS__; -} -``` - -#### 2.2. `chatDetailHandler.js` (сложный, 186 строк) -**Экспорты**: -```javascript -export function initChatDetailHandler(options = {}) { - // ... инициализация - return { - markRead, - observeLastForeign, - autoscroll, - atBottom, - destroy - }; -} -``` - -**Опции**: -```javascript -{ - scrollContainerId: 'chatScroll', - textareaId: 'id_content', - formId: 'chatForm', - scrollBtnId: 'scrollBtn', - chatId: null, - meId: null, - markReadUrl: null // или авто-определение -} -``` - -#### 2.3. `chatWebSocket.js` (сложный, 161 строка) -**Экспорты**: -```javascript -export function initChatWebSocket(options = {}) { - // ... WebSocket логика - return { - send, - close, - reconnect, - ws // прямой доступ к WebSocket - }; -} -``` - -**Опции**: -```javascript -{ - chatId: null, // REQUIRED - meId: null, // REQUIRED - scrollContainerId: 'chatScroll', - formId: 'chatForm', - textareaId: 'id_content', - typingIndicatorId: 'typing', - avatars: window.__CHAT_AVATARS__, - markReadHandler: window.__CHAT_MARK_READ__ // или передать явно -} -``` - -#### 2.4. `chatListFilter.js` (средний, 42 строки) -**Экспорты**: -```javascript -export function initChatListFilter(options = {}) { - return { - applyFilter, - setActiveType, - destroy - }; -} -``` - -#### 2.5. `chatListRealtime.js` (средний, 62 строки) -**Экспорты**: -```javascript -export function initChatListRealtime(options = {}) { - return { - ws, - disconnect, - reconnect - }; -} -``` - -#### 2.6. `chatBadgeUpdater.js` (средний, 60 строк) -**Экспорты**: -```javascript -export function initChatBadgeUpdater(options = {}) { - return { - setBadge, - recompute, - getChatUnread, - destroy - }; -} -``` - -### Шаг 3: Интеграция в base.html - -Заменить все 6 блоков на: - -```django -{% load static %} - -{# Chat functionality - только для авторизованных #} -{% if user.is_authenticated %} - -{% endif %} -``` - -### Шаг 4: Тестирование - -Проверить каждый сценарий: - -- [ ] Открытие страницы детального чата - - [ ] Автоскролл к последнему сообщению - - [ ] Разделитель "Новые сообщения" появляется - - [ ] Textarea автоматически растет при вводе - - [ ] Ctrl+Enter отправляет сообщение - - [ ] Кнопка "вниз" появляется при скролле вверх - -- [ ] Отправка сообщений - - [ ] Сообщение отправляется через WebSocket - - [ ] Сообщение появляется в чате - - [ ] Аватар и имя отображаются правильно - - [ ] Время форматируется корректно - -- [ ] Получение сообщений - - [ ] Новые сообщения приходят в реальном времени - - [ ] Автоскролл работает (если в конце списка) - - [ ] Индикация "печатает..." работает - - [ ] Отметка прочитанных срабатывает - -- [ ] Список чатов - - [ ] Поиск работает - - [ ] Фильтр по типу работает - - [ ] Пустые секции скрываются - - [ ] Preview/время обновляются при новом сообщении - - [ ] Счетчик непрочитанных обновляется - - [ ] Сортировка по времени работает - -- [ ] Глобальный счетчик - - [ ] Бейдж обновляется при новых сообщениях - - [ ] Бейдж обнуляется при прочтении - - [ ] События `chat:read` обрабатываются - -### Шаг 5: Документация - -- [ ] JSDoc для всех публичных функций -- [ ] README.md в `static/js/components/chat/` -- [ ] Примеры использования -- [ ] Описание событий и API - ---- - -## Технические детали - -### Зависимости между модулями - -``` -chatAvatars.js - ↓ -chatWebSocket.js ← chatDetailHandler.js - ↓ ↓ -chatListRealtime.js → chatBadgeUpdater.js - ↓ -chatListFilter.js -``` - -### Глобальные объекты (для обратной совместимости) - -```javascript -window.__CHAT_AVATARS__ = {}; // карта аватаров -window.__CHAT_MARK_READ__ = {}; // методы chatDetailHandler -``` - -### События - -**Генерируются**: -- `chat:read` - CustomEvent при отметке прочитанного - ```javascript - window.dispatchEvent(new CustomEvent('chat:read', { - detail: { chatId: '123' } - })); - ``` - -**Обрабатываются**: -- `chat:read` - в chatBadgeUpdater для обнуления счетчика - -### WebSocket endpoints - -1. `/ws/chat/{id}/` - детальный чат (сообщения) - - Отправка: `{ content: "текст" }` или `{ type: 'typing' }` - - Получение: `{ type: 'message', payload: {...} }` - -2. `/ws/chats/` - список чатов (обновления) - - Получение: `{ type: 'list_update', chat_id: 123, message: {...} }` - -### API endpoints - -- `POST /communications/chat/{id}/mark-read/` - - Body: `upto_ts=1234567890` - - Response: 200 OK - ---- - -## Оценка сложности - -**Время**: ~2-3 часа чистого кодинга + тестирование - -**Приоритет модулей** (по сложности): - -1. ✅ `chatAvatars.js` - тривиальный (5 минут) -2. ⚠️ `chatListFilter.js` - простой (15 минут) -3. ⚠️ `chatBadgeUpdater.js` - средний (30 минут) -4. ⚠️ `chatListRealtime.js` - средний (30 минут) -5. 🔴 `chatDetailHandler.js` - сложный (60 минут) -6. 🔴 `chatWebSocket.js` - сложный (60 минут) - -**Рекомендуемый порядок**: -1. chatAvatars (быстрая победа) -2. chatListFilter (независимый) -3. chatBadgeUpdater (зависит от chatListRealtime событий) -4. chatListRealtime (нужен для badgeUpdater) -5. chatDetailHandler (нужен для WebSocket) -6. chatWebSocket (последний, самый сложный) - ---- - -## Потенциальные проблемы - -1. **Порядок инициализации**: chatWebSocket зависит от chatDetailHandler - - Решение: передавать chatDetail как параметр в chatWebSocket - -2. **Django template context**: chatId, meId нужны из контекста - - Решение: передавать через параметры при инициализации - -3. **HTML структура**: модули зависят от конкретных селекторов - - Решение: делать проверки `if (!element) return null;` - -4. **WebSocket reconnect**: при потере соединения нужно перезагрузить страницу - - Решение: оставить `setTimeout(() => location.reload(), 2000)` - -5. **Обратная совместимость**: другие скрипты могут зависеть от `window.__CHAT_MARK_READ__` - - Решение: экспортировать в window для совместимости - ---- - -## Результат - -**До рефакторинга**: -- base.html: 521 строка встроенного JS -- 0 переиспользуемых модулей -- Нет документации -- Невозможно тестировать изолированно - -**После рефакторинга**: -- base.html: ~40 строк инициализации -- 6 переиспользуемых модулей (~600 строк с документацией) -- JSDoc для всех публичных функций -- Возможность unit-тестирования -- Четкое разделение ответственности - -**Сокращение**: 521 → 40 строк = **92% reduction** 🎉 diff --git a/backend/docs/JS_MODULES_GUIDE.md b/backend/docs/JS_MODULES_GUIDE.md deleted file mode 100644 index f11bc80f..00000000 --- a/backend/docs/JS_MODULES_GUIDE.md +++ /dev/null @@ -1,792 +0,0 @@ -# 📘 Руководство по JavaScript модулям EUSRR - -**Версия**: 1.0 -**Дата**: 4 ноября 2025 г. - ---- - -## 📚 Содержание - -1. [Обзор архитектуры](#обзор-архитектуры) -2. [Утилиты (utils)](#утилиты) -3. [Компоненты (components)](#компоненты) -4. [Примеры использования](#примеры-использования) -5. [Best Practices](#best-practices) - ---- - -## 🏗️ Обзор архитектуры - -### Структура файлов - -``` -static/js/ -├── utils/ # Утилитарные функции -│ ├── stringUtils.js # Работа со строками -│ ├── timing.js # Debounce, throttle, delay -│ └── scroll.js # Управление скроллом -└── components/ # Переиспользуемые компоненты - ├── calendarWidget.js # Виджет календаря - ├── deptRoleEditor.js # Редактор ролей отделов - ├── employeeFormHandler.js # Форма сотрудника - ├── employeeGroupsManager.js # Группы сотрудника - ├── groupPickers.js # Выбор групп должностей - ├── headPicker.js # Autocomplete руководителя - ├── likeHandler.js # Обработчик лайков - ├── listFilter.js # Фильтрация списков - ├── navbarHeight.js # CSS переменная navbar - ├── positionManager.js # Управление должностями - ├── roleManager.js # Управление ролями - ├── skillsManager.js # Управление навыками - ├── teamWheel.js # Колесо команды - ├── textareaAutogrow.js # Автоувеличение textarea - └── index.js # Экспорт всех компонентов -``` - ---- - -## 🛠️ Утилиты - -### `stringUtils.js` - -Утилиты для работы со строками и HTML. - -#### `esc(s: string): string` -Экранирует HTML-символы для безопасного вывода. - -```javascript -import { esc } from '{% static "js/utils/stringUtils.js" %}'; - -const userInput = ''; -const safe = esc(userInput); -// => '<script>alert("xss")</script>' -``` - -#### `norm(s: string): string` -Нормализует строку для поиска (lowercase, trim). - -```javascript -import { norm } from '{% static "js/utils/stringUtils.js" %}'; - -const search = norm(' Hello World '); -// => 'hello world' -``` - -#### `escAttr(s: string): string` -Экранирует строку для использования в атрибутах. - -```javascript -const attr = escAttr('some "quoted" text'); -// => 'some "quoted" text' -``` - -#### `truncate(s: string, maxLen: number): string` -Обрезает строку до указанной длины с добавлением "...". - -```javascript -const short = truncate('Very long text here', 10); -// => 'Very lon...' -``` - -#### `getInitials(name: string): string` -Получает инициалы из имени. - -```javascript -const initials = getInitials('Иван Петрович Сидоров'); -// => 'ИПС' -``` - ---- - -### `timing.js` - -Утилиты для управления временем выполнения. - -#### `debounce(fn: Function, ms: number): Function` -Откладывает выполнение функции до прекращения вызовов. - -```javascript -import { debounce } from '{% static "js/utils/timing.js" %}'; - -const search = debounce((query) => { - console.log('Searching for:', query); -}, 300); - -input.addEventListener('input', (e) => search(e.target.value)); -``` - -#### `throttle(fn: Function, ms: number): Function` -Ограничивает частоту вызова функции. - -```javascript -import { throttle } from '{% static "js/utils/timing.js" %}'; - -const onScroll = throttle(() => { - console.log('Scroll position:', window.scrollY); -}, 100); - -window.addEventListener('scroll', onScroll); -``` - -#### `delay(ms: number): Promise` -Промисифицированная задержка. - -```javascript -import { delay } from '{% static "js/utils/timing.js" %}'; - -async function animateSequence() { - element.classList.add('step1'); - await delay(300); - element.classList.add('step2'); - await delay(300); - element.classList.add('complete'); -} -``` - ---- - -### `scroll.js` - -Утилиты для управления скроллом. - -#### `smoothScrollTo(element: HTMLElement, options?: Object): void` -Плавно прокручивает к элементу. - -```javascript -import { smoothScrollTo } from '{% static "js/utils/scroll.js" %}'; - -const target = document.getElementById('section'); -smoothScrollTo(target, { - offset: 80, // Отступ сверху - duration: 500 // Длительность анимации -}); -``` - -#### `scrollToTop(duration: number = 300): void` -Плавно прокручивает страницу наверх. - -```javascript -button.addEventListener('click', () => scrollToTop(500)); -``` - -#### `isElementInViewport(el: HTMLElement): boolean` -Проверяет, виден ли элемент в окне просмотра. - -```javascript -if (isElementInViewport(element)) { - element.classList.add('animate'); -} -``` - ---- - -## 🎨 Компоненты - -### `navbarHeight.js` - -Устанавливает CSS-переменную с высотой навбара. - -#### API - -```javascript -import { initNavbarHeight } from '{% static "js/components/navbarHeight.js" %}'; - -const navbarHeight = initNavbarHeight(); -// Автоматически создаёт CSS переменную --navbar-height -``` - -**Использование в CSS:** -```css -.content { - margin-top: var(--navbar-height); -} -``` - ---- - -### `likeHandler.js` - -Глобальный обработчик лайков для постов и комментариев. - -#### API - -```javascript -import { initLikeHandler } from '{% static "js/components/likeHandler.js" %}'; - -const likeHandler = initLikeHandler({ - postUrl: '/api/v1/feed/posts/{id}/like/', - commentUrl: '/api/v1/feed/comments/{id}/like/' -}); -``` - -#### HTML разметка - -```django - -``` - -#### Возвращаемый API - -- `refresh()` - переинициализация обработчиков - ---- - -### `textareaAutogrow.js` - -Автоматически увеличивает высоту textarea при вводе текста. - -#### API - -```javascript -import { initTextareaAutogrow } from '{% static "js/components/textareaAutogrow.js" %}'; - -const autogrow = initTextareaAutogrow({ - selector: '[data-autogrow]', - minHeight: 40, - maxHeight: 300 -}); -``` - -#### HTML разметка - -```html - -``` - -#### Возвращаемый API - -- `refresh()` - переинициализация для новых элементов -- `destroy()` - отключение обработчиков - ---- - -### `employeeFormHandler.js` - -Управляет формой сотрудника (валидация, изменения, AJAX). - -#### API - -```javascript -import { initEmployeeForm } from '{% static "js/components/employeeFormHandler.js" %}'; - -const employeeForm = initEmployeeForm(); -``` - -#### Возможности - -- ✅ Отслеживание изменений полей -- ✅ Валидация перед отправкой -- ✅ Предупреждение о несохранённых изменениях -- ✅ AJAX отправка формы -- ✅ Обработка ошибок - -#### Возвращаемый API - -- `hasChanges()` - проверка наличия изменений -- `resetTracking()` - сброс отслеживания -- `validateForm()` - валидация формы -- `isDirty` - флаг изменений - ---- - -### `positionManager.js` - -Управляет CRUD операциями с должностями через модальные окна. - -#### API - -```javascript -import { initPositionManager } from '{% static "js/components/positionManager.js" %}'; - -const positionMgr = initPositionManager(); -``` - -#### HTML требования - -- `#positionBlock[data-update-url-tpl]` - шаблон URL обновления -- `#positionBlock[data-detail-url-tpl]` - шаблон URL получения -- `#positionBlock[data-create-url]` - URL создания -- Модалы: `#posEditModal`, `#posCreateModal` - -#### Возвращаемый API - -- `refresh()` - переинициализация -- `openCreateModal()` - открыть модал создания -- `openEditModal(positionId)` - открыть модал редактирования - ---- - -### `groupPickers.js` - -Пикеры для выбора групп должностей (создание и редактирование). - -#### API - -```javascript -import { - initPositionGroupPicker, - initPositionEditGroupPicker -} from '{% static "js/components/groupPickers.js" %}'; - -// Для формы создания должности -const createPicker = initPositionGroupPicker(); - -// Для формы редактирования должности -const editPicker = initPositionEditGroupPicker(); -``` - -#### Возможности - -- ✅ Выбор нескольких групп -- ✅ Отображение чипов выбранных групп -- ✅ Поиск по группам -- ✅ Синхронизация с hidden input'ами - ---- - -### `employeeGroupsManager.js` - -Управляет персональными группами прав сотрудника. - -#### API - -```javascript -import { initEmployeeGroupsManager } from '{% static "js/components/employeeGroupsManager.js" %}'; - -const groupsMgr = initEmployeeGroupsManager(); -``` - -#### HTML требования - -- `#empGroupsAssignBtn` - кнопка назначения групп -- `#empGroupsList` - список текущих групп -- `#empGroupsModal` - модал выбора групп - -#### Возвращаемый API - -- `refresh()` - обновление списка -- `assignGroups(groupIds)` - назначить группы -- `removeGroup(groupId)` - удалить группу - ---- - -### `skillsManager.js` - -Управляет навыками сотрудника (добавление/удаление через AJAX). - -#### API - -```javascript -import { initSkillsManager } from '{% static "js/components/skillsManager.js" %}'; - -const skillsManager = initSkillsManager({ - blockId: 'skillsBlock', - formId: 'skillAddForm', - collapseId: 'skillsForm', - removeUrl: '/employees/1/skills/remove/' -}); -``` - -#### Возможности - -- ✅ AJAX добавление навыков -- ✅ AJAX удаление по клику на чип -- ✅ Анимация добавления/удаления -- ✅ Валидация дубликатов - -#### Возвращаемый API - -- `addSkill(name)` - добавить навык (возвращает Promise) -- `refresh()` - переинициализация - ---- - -### `deptRoleEditor.js` - -Редактор роли сотрудника в отделе с валидацией изменений. - -#### API - -```javascript -import { initDeptRoleEditor } from '{% static "js/components/deptRoleEditor.js" %}'; - -const deptRoleEditor = initDeptRoleEditor(); -``` - -#### HTML разметка - -```html -
- - -
-``` - -#### Возможности - -- ✅ Активация кнопки OK только при изменении -- ✅ Инициализация tooltip'ов -- ✅ Множественные экземпляры на странице - -#### Возвращаемый API - -- `instances` - массив всех редакторов -- `refresh()` - переинициализация - ---- - -### `headPicker.js` - -Autocomplete для выбора руководителя отдела по имени или email. - -#### API - -```javascript -import { initHeadPicker } from '{% static "js/components/headPicker.js" %}'; - -const headPicker = initHeadPicker({ - selector: '[data-head-picker]' -}); -``` - -#### HTML требования - -```html -
- - - -
-``` - -#### Возможности - -- ✅ Поиск по имени и email -- ✅ Валидация выбора при submit -- ✅ Автозаполнение при загрузке -- ✅ Клавиатурная навигация (Enter, Escape) - ---- - -### `roleManager.js` - -Управление ролями и правами отделов (создание, редактирование, удаление). - -#### API - -```javascript -import { initRoleManager } from '{% static "js/components/roleManager.js" %}'; - -const roleManager = initRoleManager(); -``` - -#### Возможности - -- ✅ Autocomplete выбора роли -- ✅ Управление правами через чипы -- ✅ Создание новой роли -- ✅ Редактирование существующей роли -- ✅ Удаление роли с подтверждением -- ✅ Модалы с выбором прав (permissions) - -#### Возвращаемый API - -- `refresh()` - переинициализация -- `getSelectedRole()` - получить выбранную роль -- `getEditPermissions()` - получить выбранные права редактирования -- `getCreatePermissions()` - получить выбранные права создания - ---- - -### `calendarWidget.js` - -Полнофункциональный виджет календаря с FullCalendar.js. - -#### API - -```javascript -import { initCalendarWidget } from '{% static "js/components/calendarWidget.js" %}'; - -const widget = initCalendarWidget(); -``` - -#### Возможности - -- ✅ Отображение событий -- ✅ Создание новых событий -- ✅ Редактирование событий -- ✅ Drag & drop событий -- ✅ Интеграция с модалами -- ✅ AJAX сохранение - ---- - -### `teamWheel.js` - -Анимированное колесо команды отдела с автоскроллом. - -#### API - -```javascript -import { initTeamWheel } from '{% static "js/components/teamWheel.js" %}'; - -const wheel = initTeamWheel({ - wheelId: 'teamWheel-1', - dataSourceId: 'teamData-1', - fallback: { - avatar: '/static/img/default-avatar.png', - name: 'Руководитель', - role: 'Руководитель отдела' - } -}); -``` - -#### Возвращаемый API - -- `start()` - запустить автоскролл -- `stop()` - остановить автоскролл -- `next()` - следующий участник -- `prev()` - предыдущий участник - ---- - -### `listFilter.js` - -Универсальный компонент фильтрации списков. - -#### API - -```javascript -import { ListFilter, createDataAttrMatcher } from '{% static "js/components/listFilter.js" %}'; - -new ListFilter({ - listSelector: '#empList', - itemSelector: '.emp-row', - searchInputSelector: '#empFilter', - clearButtonSelector: '.ios-search-clear', - matchFn: createDataAttrMatcher(['name', 'depts']), - debounceMs: 300 -}); -``` - -#### Возможности - -- ✅ Debounced поиск -- ✅ Кастомная функция сопоставления -- ✅ Подсчёт результатов -- ✅ Кнопка очистки -- ✅ Клавиатурные события - ---- - -## 💡 Примеры использования - -### Пример 1: Базовая инициализация в шаблоне - -```django -{% load static %} - -``` - -### Пример 2: Множественная инициализация - -```django - -``` - -### Пример 3: Работа с API модуля - -```javascript -import { initEmployeeForm } from '/static/js/components/employeeFormHandler.js'; - -const form = initEmployeeForm(); - -// Проверка изменений перед уходом со страницы -window.addEventListener('beforeunload', (e) => { - if (form && form.hasChanges()) { - e.preventDefault(); - e.returnValue = 'У вас есть несохранённые изменения'; - } -}); - -// Программная отправка формы -document.getElementById('customSaveBtn').addEventListener('click', async () => { - if (form && form.validateForm()) { - try { - await form.submit(); - alert('Сохранено!'); - } catch (error) { - alert('Ошибка: ' + error.message); - } - } -}); -``` - ---- - -## ✅ Best Practices - -### 1. Всегда проверяйте возвращаемое значение - -```javascript -const component = initSomeComponent(); -if (component) { - // Компонент успешно инициализирован - component.doSomething(); -} else { - // Необходимые элементы не найдены - console.warn('Component not initialized'); -} -``` - -### 2. Используйте DOMContentLoaded - -```javascript -document.addEventListener('DOMContentLoaded', () => { - // Инициализация после загрузки DOM - initAllComponents(); -}); -``` - -### 3. Экспортируйте в window для отладки - -```javascript -const component = initComponent(); -if (component) { - window.debugComponent = component; // Доступ из консоли -} -``` - -### 4. Группируйте импорты - -```javascript -// Utils -import { esc, norm } from '{% static "js/utils/stringUtils.js" %}'; -import { debounce } from '{% static "js/utils/timing.js" %}'; - -// Components -import { initEmployeeForm } from '{% static "js/components/employeeFormHandler.js" %}'; -import { initPositionManager } from '{% static "js/components/positionManager.js" %}'; -``` - -### 5. Используйте деструктуризацию - -```javascript -// ✅ Хорошо -import { initComponent } from './component.js'; - -// ❌ Плохо -import * as Component from './component.js'; -const init = Component.initComponent; -``` - -### 6. Обрабатывайте ошибки - -```javascript -try { - const result = await component.someAsyncOperation(); - handleSuccess(result); -} catch (error) { - console.error('Operation failed:', error); - handleError(error); -} -``` - -### 7. Очищайте ресурсы - -```javascript -// Если модуль возвращает destroy() -const component = initComponent(); - -// При удалении со страницы -function cleanup() { - if (component && component.destroy) { - component.destroy(); - } -} -``` - ---- - -## 🔧 Отладка - -### Доступ к компонентам из консоли - -Все инициализированные компоненты доступны через `window`: - -```javascript -// В консоли браузера -window.employeeForm.hasChanges() // true/false -window.positionManager.refresh() // переинициализация -window.likeHandler // объект API -``` - -### Логирование - -Модули используют `console.warn()` для предупреждений: - -```javascript -// Если элементы не найдены -console.warn('EmployeeForm: required elements not found'); -``` - -### Проверка инициализации - -```javascript -// Проверить, был ли модуль инициализирован -if (window.__employeeFormInitialized) { - console.log('EmployeeForm already initialized'); -} -``` - ---- - -## 📞 Поддержка - -При возникновении вопросов: -1. Проверьте JSDoc в исходном коде модуля -2. Изучите примеры использования в шаблонах -3. Проверьте консоль браузера на предупреждения -4. Используйте `window.componentName` для отладки - ---- - -**Дата актуализации**: 4 ноября 2025 г. -**Версия**: 1.0 diff --git a/backend/docs/LDAP_CONFIGURATION.md b/backend/docs/LDAP_CONFIGURATION.md deleted file mode 100644 index 1687af68..00000000 --- a/backend/docs/LDAP_CONFIGURATION.md +++ /dev/null @@ -1,238 +0,0 @@ -# Конфигурация LDAP в системе EUSRR - -## Обзор - -Система EUSRR поддерживает два режима работы: -1. **С интеграцией LDAP/Active Directory** (по умолчанию) -2. **Без LDAP** (автономный режим с аутентификацией через БД) - -Переключение между режимами осуществляется через переменную окружения `LDAP_ENABLED`. - -## Режимы работы - -### Режим с LDAP (LDAP_ENABLED=true) - -**Аутентификация:** -- Пользователи аутентифицируются через Active Directory/LDAP -- Пароли проверяются в LDAP, не хранятся в БД -- Автоматическая синхронизация профилей при входе - -**Создание пользователей:** -- Пользователи создаются сначала в LDAP, затем в БД -- Устанавливается флаг `is_ldap_managed=True` -- DN и GUID пользователя сохраняются в `LdapSyncState` - -**Обновление пользователей:** -- Изменения применяются сначала к LDAP, затем к БД -- Поддерживается двусторонняя синхронизация (LWW - Last Writer Wins) - -**Бэкенды аутентификации (в порядке приоритета):** -1. `LDAP3Backend` - основной LDAP-бэкенд -2. `EmailOrPhoneBackend` - пропускается (так как LDAP активен) -3. `SuperuserOnlyBackend` - экстренный доступ для суперюзера -4. `PositionRoleBackend` - расчёт прав -5. `ModelBackend` - стандартный Django (как фоллбэк) - -**Требуемые настройки в .env:** -```env -LDAP_ENABLED=true -LDAP_URI=ldaps://dcii.robotail.local:636 -LDAP_BIND_DN=corp@robotail.local -LDAP_BIND_PASSWORD=your_password -LDAP_USER_BASE="OU=company,DC=robotail,DC=local" -LDAP_USERS_BASE="OU=Users,OU=company,DC=robotail,DC=local" -LDAP_USER_UPN_SUFFIX=@robotail.local -LDAP_WRITE_ENABLED=true -``` - -### Режим без LDAP (LDAP_ENABLED=false) - -**Аутентификация:** -- Пользователи аутентифицируются через БД Django -- Пароли хешируются и хранятся в таблице `employees_employee` -- Используется стандартная система паролей Django - -**Создание пользователей:** -- Пользователи создаются напрямую в БД -- Флаг `is_ldap_managed=False` -- Пароль устанавливается через `user.set_password()` - -**Обновление пользователей:** -- Все изменения применяются только к БД -- Синхронизация с LDAP не выполняется - -**Бэкенды аутентификации (в порядке приоритета):** -1. `LDAP3Backend` - пропускает запрос (LDAP_ENABLED=false) -2. `EmailOrPhoneBackend` - **основной бэкенд** в этом режиме -3. `SuperuserOnlyBackend` - экстренный доступ для суперюзера -4. `PositionRoleBackend` - расчёт прав -5. `ModelBackend` - стандартный Django - -**Требуемые настройки в .env:** -```env -LDAP_ENABLED=false -``` - -## Переключение между режимами - -### Включение LDAP - -1. В `.env` установите: - ```env - LDAP_ENABLED=true - ``` - -2. Настройте параметры подключения к LDAP (см. выше) - -3. Перезапустите Django: - ```bash - python manage.py runserver - ``` - -4. **Важно:** Существующие пользователи с `is_ldap_managed=False` продолжат работать через БД до тех пор, пока не будут мигрированы в LDAP - -### Отключение LDAP - -1. В `.env` установите: - ```env - LDAP_ENABLED=false - ``` - -2. Перезапустите Django - -3. **Важно:** Пользователи с `is_ldap_managed=True` НЕ СМОГУТ войти в систему, так как их пароли хранятся в LDAP. Для восстановления доступа необходимо: - - Установить пароль вручную: `user.set_password('new_password')` - - Изменить флаг: `user.is_ldap_managed = False` - - Сохранить: `user.save()` - -## Миграция пользователей - -### Из БД в LDAP - -```python -from employees.ldap.directory_service import DirectoryService -from employees.ldap.domain.dtos import DirectoryUserDTO - -# Для каждого пользователя -dto = DirectoryUserDTO( - first_name=user.first_name, - last_name=user.last_name, - email=user.email, - phone_e164=user.phone_number, - initial_password="temporary_password", - is_active=user.is_active, -) - -service = DirectoryService() -service.create_user(dto) -# Пользователь будет создан в LDAP и помечен как is_ldap_managed=True -``` - -### Из LDAP в БД - -```python -# Для каждого LDAP-пользователя -user.set_password('new_password') # Устанавливаем пароль в БД -user.is_ldap_managed = False -user.save() -``` - -## Проверка текущего режима - -В коде Django: -```python -from django.conf import settings - -if settings.LDAP_ENABLED: - print("Режим: с LDAP") -else: - print("Режим: без LDAP") -``` - -В Django shell: -```bash -python manage.py shell ->>> from django.conf import settings ->>> settings.LDAP_ENABLED -True # или False -``` - -## Рекомендации по безопасности - -1. **Для продакшена с LDAP:** - - Используйте LDAPS (порт 636) - - Проверяйте сертификат CA - - Используйте отдельную сервисную учётку с минимальными правами - - Включите `LDAP_WRITE_ENABLED` только если нужна запись в LDAP - -2. **Для продакшена без LDAP:** - - Настройте строгие требования к паролям в Django - - Включите двухфакторную аутентификацию - - Регулярно аудитируйте доступы - - Используйте `PASSWORD_HASHERS` с современными алгоритмами - -3. **Для разработки:** - - Можно отключить LDAP для упрощения разработки - - Создавайте тестовых пользователей напрямую в БД - - Не храните реальные LDAP-креды в репозитории - -## Поддерживаемые операции - -| Операция | С LDAP | Без LDAP | -|----------|--------|----------| -| Создание пользователя | LDAP → БД | БД | -| Аутентификация | LDAP | БД (Django) | -| Обновление профиля | LDAP → БД | БД | -| Смена пароля | LDAP | БД | -| Удаление пользователя | LDAP → БД | БД | -| Синхронизация групп | Да | Нет | -| Синхронизация отделов | Да | Нет | - -## Устранение неполадок - -### Ошибка: "LDAP_BASE_DN must be set" - -**Причина:** Не настроены переменные LDAP в settings.py или .env - -**Решение:** -1. Проверьте наличие в `.env`: - ```env - LDAP_USERS_BASE="OU=Users,OU=company,DC=robotail,DC=local" - ``` -2. Или отключите LDAP: - ```env - LDAP_ENABLED=false - ``` - -### Ошибка: "LDAP create failed: ..." - -**Причина:** Проблема подключения или прав в LDAP - -**Решение:** -1. Проверьте сетевое подключение к LDAP-серверу -2. Проверьте права сервисной учётки -3. Временно отключите LDAP для продолжения работы - -### Пользователи не могут войти после отключения LDAP - -**Причина:** У пользователей установлен `is_ldap_managed=True`, но LDAP недоступен - -**Решение:** -```python -# В Django shell -from employees.models import Employee - -# Для конкретного пользователя -user = Employee.objects.get(email='user@example.com') -user.set_password('new_password') -user.is_ldap_managed = False -user.save() - -# Или массово -Employee.objects.filter(is_ldap_managed=True).update(is_ldap_managed=False) -# Затем установить пароли вручную каждому -``` - -## Контакты - -При возникновении проблем с интеграцией LDAP обращайтесь к системному администратору. diff --git a/backend/docs/README.md b/backend/docs/README.md deleted file mode 100644 index 8d5c129e..00000000 --- a/backend/docs/README.md +++ /dev/null @@ -1,92 +0,0 @@ -# Backend Documentation - -Техническая документация по backend части проекта EUSRR. - -## 📁 Структура - -### [guides/](guides/) -**Руководства разработчика** - инструкции по работе с различными модулями - -- CONTEXT_MENU_GUIDE.md - работа с контекстными меню -- REACTIONS_GUIDE.md - система реакций - -### [troubleshooting/](troubleshooting/) -**Отладка и исправления** - диагностика проблем - -- CONTEXT_MENU_DEBUG.md - отладка контекстного меню -- MESSAGE_DELETE_DEBUG.md - отладка удаления сообщений -- MESSAGE_DELETE_FIX.md - исправление проблем удаления - -### [architecture/](architecture/) -**Архитектурная документация** - устройство систем - -- API_ANALYSIS.md - анализ API -- API_REFACTORING_SUMMARY.md - результаты рефакторинга API -- VIEWSET_ANALYSIS.md - анализ ViewSet - -### [testing/](testing/) -**Тестирование** - планы и результаты тестирования - -- POSITION_PERMISSIONS_TESTING.md - тестирование прав должностей -- REQUESTS_API_TESTING_PLAN.md - план тестирования API заявок -- TEST_IMAGE_SCRIPTS.md - скрипты тестирования изображений - -### [reports/](reports/) -**Отчеты и аудиты** - результаты проверок и реорганизаций - -- BACKEND_CLEANUP_AUDIT.md - аудит очистки backend/ -- REQUESTS_API_TESTING_PLAN.md - план тестирования API заявок -- TEST_IMAGE_SCRIPTS.md - скрипты тестирования изображений - -## 🔍 Навигация - -### По типу задачи: - -- **Как работает X?** → [architecture/](architecture/) -- **Как настроить Y?** → [guides/](guides/) -- **Проблема с Z** → [troubleshooting/](troubleshooting/) -- **Как тестировать?** → [testing/](testing/) - -### Связанная документация: - -- [/docs/](../../docs/) - общая документация проекта -- [backend/scripts/](../scripts/) - вспомогательные скрипты - -## 📝 Правила - -### Добавление новой документации: - -1. **Guides** - постоянные инструкции, reference материалы -2. **Troubleshooting** - временные документы, после решения → удалить -3. **Architecture** - долгосрочная документация, обновлять при изменениях -4. **Testing** - планы тестирования, после завершения → архивировать - -### Формат файлов: - -```markdown -# Заголовок - -**Дата:** DD.MM.YYYY -**Автор:** Имя -**Статус:** Актуально / Устарело / В процессе - -## Описание -... - -## Использование -... - -## Примеры -... -``` - -## 🔄 Жизненный цикл - -1. **Создание** - документируйте новую фичу/проблему -2. **Актуализация** - обновляйте при изменениях -3. **Архивация** - после завершения/решения -4. **Удаление** - если больше не актуально - ---- - -**Поддерживайте документацию актуальной!** 📚 diff --git a/backend/docs/REFACTORING_SUMMARY.md b/backend/docs/REFACTORING_SUMMARY.md deleted file mode 100644 index 8691e0c5..00000000 --- a/backend/docs/REFACTORING_SUMMARY.md +++ /dev/null @@ -1,319 +0,0 @@ -# 📊 Итоги рефакторинга EUSRR Frontend - -**Дата завершения**: 4 ноября 2025 г. -**Ветка**: `merge/master-and-ldap-writeback` - ---- - -## 🎯 Цели рефакторинга - -1. ✅ Извлечь встроенный JavaScript в переиспользуемые ES6 модули -2. ✅ Декомпозировать большие HTML-шаблоны на логические компоненты -3. ✅ Улучшить читаемость и поддерживаемость кода -4. ✅ Создать документацию для разработчиков - ---- - -## 📈 Статистика изменений - -### JavaScript модули (Фаза 4) - -**Создано 11 новых модулей** (всего 15 в `static/js/components/`): - -| Модуль | Строки | Назначение | -|--------|--------|------------| -| `employeeFormHandler.js` | 280 | Управление формой сотрудника | -| `positionManager.js` | 380 | CRUD должностей с модалами | -| `groupPickers.js` | 238 | Выбор групп должностей | -| `employeeGroupsManager.js` | 341 | Управление группами сотрудника | -| `navbarHeight.js` | 35 | CSS переменная высоты navbar | -| `likeHandler.js` | 145 | Глобальный обработчик лайков | -| `skillsManager.js` | 245 | Управление навыками сотрудника | -| `deptRoleEditor.js` | 65 | Редактор ролей в отделах | -| `textareaAutogrow.js` | 140 | Автоувеличение textarea | -| `headPicker.js` | 203 | Autocomplete выбора руководителя | -| `roleManager.js` | 393 | Управление ролями отделов | - -**Итого**: 2465 строк модульного кода - -**Уже существующие модули**: -- `calendarWidget.js` (38 КБ) -- `teamWheel.js` (11 КБ) -- `listFilter.js` (8.6 КБ) -- `index.js` (1.3 КБ) - ---- - -### Сокращение шаблонов - -#### Основные файлы - -| Файл | Было | Стало | Изменение | % | -|------|------|-------|-----------|---| -| `employee_form_scripts.html` | 907 | 22 | **-885** | **-98%** | -| `employee_detail.html` | 600 | 432 | -168 | -28% | -| `_department_controls.html` | 621 | 275 | -346 | -56% | -| `base.html` | ~710 | 631 | -79 | -11% | - -**Итого удалено из шаблонов**: ~1478 строк встроенного JavaScript - -#### Декомпозированные шаблоны (Фаза 5) - -| Шаблон | Размер | Статус | Компоненты | -|--------|--------|--------|------------| -| `employee_edit.html` | **24 строки** | ✅ Готов | employee_form_* (7 компонентов) | -| `department_detail.html` | **46 строк** | ✅ Готов | department_* (4 компонента) | -| `rightbar_calendar.html` | **17 строк** | ✅ Готов | calendar_* (5 компонентов) | - ---- - -### Структура компонентов - -#### Компоненты сотрудников -``` -templates/employees/components/ -├── employee_form_personal.html # Личные данные -├── employee_form_contacts.html # Контакты -├── employee_form_position.html # Должность -├── employee_form_employment.html # Трудоустройство -├── employee_form_photo.html # Фотография -├── employee_form_actions.html # Кнопки действий -├── employee_form_scripts.html # JS инициализация (22 строки) -├── employee_detail_scripts.html # JS для деталей (47 строк) -├── department_controls_scripts.html # JS управления отделом (19 строк) -└── department_scripts.html # JS отдела (304 строки) -``` - -#### Компоненты календаря -``` -templates/includes/components/ -├── calendar_desktop.html # 40 строк - десктопная карточка -├── calendar_mobile.html # 23 строки - мобильная offcanvas -├── calendar_modal_create.html # 132 строки - создание события -├── calendar_modal_details.html # 33 строки - просмотр события -└── calendar_scripts.html # 11 строк - инициализация -``` - ---- - -## 🏗️ Архитектура модулей - -### Принципы проектирования - -1. **ES6 Modules**: Все новые модули используют `export/import` -2. **Graceful Degradation**: Возврат `null` если элементы отсутствуют -3. **Protection**: Защита от повторной инициализации -4. **JSDoc**: Полная документация API -5. **Window Export**: Публикация в `window` для обратной совместимости - -### Пример структуры модуля - -```javascript -/** - * @fileoverview Краткое описание - * @module components/moduleName - */ - -export function initModuleName(options = {}) { - // Защита от повторной инициализации - if (window.__moduleNameInitialized) { - return null; - } - - // Проверка наличия элементов - const elements = { - target: document.getElementById('targetId') - }; - - if (!elements.target) { - return null; - } - - // Логика модуля - // ... - - // API - return { - refresh: () => { /* переинициализация */ }, - getState: () => { /* получение состояния */ } - }; -} - -// Публикация для совместимости -if (typeof window !== 'undefined') { - window.initModuleName = initModuleName; -} -``` - ---- - -## 📦 Утилиты (utils/) - -### Строковые утилиты (`stringUtils.js`) -- `esc(s)` - экранирование HTML -- `norm(s)` - нормализация для поиска -- `escAttr(s)` - экранирование атрибутов -- `truncate(s, len)` - обрезка текста -- `getInitials(name)` - получение инициалов - -### Утилиты времени (`timing.js`) -- `debounce(fn, ms)` - антидребезг -- `throttle(fn, ms)` - троттлинг -- `delay(ms)` - промисификация таймаута - -### Утилиты скролла (`scroll.js`) -- `smoothScrollTo(el, options)` - плавный скролл -- `scrollToTop(duration)` - скролл наверх -- `isElementInViewport(el)` - проверка видимости - ---- - -## 🎨 CSS архитектура - -### Структура стилей -``` -static/css/ -├── variables.css # CSS переменные (цвета, отступы) -├── components/ -│ ├── common.css # Общие стили -│ ├── base-app.css # Базовая структура приложения -│ ├── ios-components.css # iOS-стайлинг -│ ├── cards.css # Карточки ленты -│ ├── ios-search.css # Поиск в iOS стиле -│ └── rightbar-calendar.css # Стили календаря -``` - -### CSS переменные -```css -:root { - --navbar-height: 56px; - --primary-color: #007aff; - --background-color: #f5f5f7; - /* ... */ -} -``` - ---- - -## 🔄 Паттерны использования - -### Инициализация в base.html -```django - -``` - -### Инициализация страничных компонентов -```django -{% include "employees/components/employee_form_scripts.html" %} -``` - ---- - -## 📋 Оставшиеся задачи - -### 🔴 Отложено на отдельные сессии - -1. **Chat Handler** (521 строка) - - 6 блоков WebSocket логики - - План: `docs/CHAT_REFACTORING_SESSION.md` - - Оценка: 2-3 часа - -2. **document_list.html** (705 строк, 419 строк JS) - - RecipientPicker класс (сложная логика) - - CRUD операции с документами - - Оценка: 3-4 часа - -3. **employee_form_position.html** (360 строк) - - Большая форма с бизнес-логикой - - Уже декомпозирована как компонент - - Дальнейшее разделение не требуется - ---- - -## ✅ Достигнутые результаты - -### Улучшение качества кода -- ✅ Модульная архитектура JavaScript -- ✅ Переиспользуемые компоненты -- ✅ Документированный API (JSDoc) -- ✅ Защита от повторной инициализации -- ✅ Graceful degradation - -### Улучшение структуры шаблонов -- ✅ Декомпозиция больших файлов (>1000 строк → <50 строк) -- ✅ Логическое разделение на компоненты -- ✅ Переиспользуемые include-блоки -- ✅ Чистое разделение HTML/JS - -### Улучшение поддерживаемости -- ✅ Локализация изменений (изменение модуля не затрагивает другие) -- ✅ Упрощение тестирования (модули можно тестировать отдельно) -- ✅ Упрощение отладки (понятная структура, явные зависимости) -- ✅ Документация для новых разработчиков - ---- - -## 📚 Документация - -Создана полная документация: -- ✅ `REFACTORING_SUMMARY.md` - итоговый отчёт (этот файл) -- ⏳ `TEMPLATES_GUIDE.md` - руководство по шаблонам -- ⏳ `JS_MODULES_GUIDE.md` - руководство по JavaScript модулям -- ✅ `CHAT_REFACTORING_SESSION.md` - план рефакторинга чата -- ✅ `CHAT_REFACTORING_QUICKSTART.md` - быстрый старт для чата - ---- - -## 🚀 Следующие шаги - -### Немедленные -1. Создать `TEMPLATES_GUIDE.md` с описанием структуры шаблонов -2. Создать `JS_MODULES_GUIDE.md` с API всех модулей -3. Протестировать все страницы после изменений - -### Среднесрочные -1. Рефакторинг Chat handler (отдельная сессия) -2. Настройка минификации JavaScript -3. Оптимизация CSS переменных - -### Долгосрочные -1. Рефакторинг document_list.html -2. Внедрение TypeScript (опционально) -3. Настройка автоматического тестирования компонентов - ---- - -## 📊 Метрики - -### До рефакторинга -- Встроенного JavaScript: ~2000+ строк -- Средний размер шаблона: 600-1400 строк -- Переиспользуемых модулей: 4 - -### После рефакторинга -- Модульного JavaScript: 4035 строк (15 модулей) -- Средний размер шаблона: 17-46 строк (основные) -- Переиспользуемых модулей: 15 -- Создано компонентов: 18+ - -### Улучшения -- **Читаемость**: +300% (оценочно) -- **Поддерживаемость**: +400% (оценочно) -- **Скорость разработки**: +250% (для новых функций) -- **Количество багов**: -50% (за счёт изоляции кода) - ---- - -**Автор**: GitHub Copilot -**Проект**: EUSRR (Employee Unified System for Resource Routing) -**Репозиторий**: IgorNadein/EUSRR diff --git a/backend/docs/TEMPLATES_GUIDE.md b/backend/docs/TEMPLATES_GUIDE.md deleted file mode 100644 index f9523287..00000000 --- a/backend/docs/TEMPLATES_GUIDE.md +++ /dev/null @@ -1,735 +0,0 @@ -# 📄 Руководство по шаблонам EUSRR - -**Версия**: 1.0 -**Дата**: 4 ноября 2025 г. - ---- - -## 📚 Содержание - -1. [Структура шаблонов](#структура-шаблонов) -2. [Базовый шаблон](#базовый-шаблон) -3. [Компоненты сотрудников](#компоненты-сотрудников) -4. [Компоненты календаря](#компоненты-календаря) -5. [Компоненты отделов](#компоненты-отделов) -6. [Best Practices](#best-practices) - ---- - -## 🏗️ Структура шаблонов - -### Общая организация - -``` -templates/ -├── base.html # Базовый шаблон (631 строка) -├── includes/ # Общие компоненты -│ ├── navbar.html # Навигационная панель -│ ├── sidebar.html # Левая боковая панель -│ ├── rightbar_calendar.html # Правая панель календаря (17 строк) -│ └── components/ # Компоненты для includes -│ ├── calendar_desktop.html # Десктопная версия календаря -│ ├── calendar_mobile.html # Мобильная offcanvas версия -│ ├── calendar_modal_create.html # Модал создания события -│ ├── calendar_modal_details.html # Модал деталей события -│ └── calendar_scripts.html # JS инициализация календаря -├── employees/ # Шаблоны сотрудников -│ ├── employees_list.html # Список сотрудников -│ ├── employee_detail.html # Детали сотрудника (432 строки) -│ ├── _employee_edit.html # Форма редактирования (24 строки!) -│ ├── _department_controls.html # Управление отделом (275 строк) -│ └── components/ # Компоненты сотрудников -│ ├── employee_form_personal.html -│ ├── employee_form_contacts.html -│ ├── employee_form_position.html -│ ├── employee_form_employment.html -│ ├── employee_form_photo.html -│ ├── employee_form_actions.html -│ ├── employee_form_scripts.html -│ ├── employee_detail_scripts.html -│ ├── department_controls_scripts.html -│ └── department_scripts.html -├── departments/ -│ ├── department_detail.html # Детали отдела (46 строк!) -│ └── components/ -│ ├── department_header.html -│ ├── department_wheel.html -│ ├── department_members.html -│ └── department_sidebar.html -├── documents/ -│ └── document_list.html # Список документов (705 строк) -├── search/ -│ └── results.html # Результаты поиска (335 строк) -└── requests_app/ - └── request_list_full.html # Список заявок (673 строки) -``` - ---- - -## 🎨 Базовый шаблон - -### `base.html` - -Основной шаблон приложения с трёхколоночной структурой. - -#### Структура - -```django - - - - - {% block title %}HiRo{% endblock %} - - - - - - - - - - - - - - {% block extra_css %}{% endblock %} - - - {% include "includes/navbar.html" %} - -
-
- - {# Левая боковая панель #} - {% if user.is_authenticated %} - {% include "includes/sidebar.html" %} - {% endif %} - - {# Основной контент #} -
- {% block content %}{% endblock %} -
- - {# Правая панель с календарём #} - {% if user.is_authenticated %} - {% include "includes/rightbar_calendar.html" %} - {% endif %} - -
-
- - - - - - - - - {% block extra_js %}{% endblock %} - - -``` - -#### Блоки для переопределения - -- `{% block title %}` - заголовок страницы -- `{% block extra_css %}` - дополнительные стили -- `{% block content %}` - основной контент -- `{% block extra_js %}` - дополнительные скрипты - ---- - -## 👥 Компоненты сотрудников - -### Форма редактирования сотрудника - -#### `_employee_edit.html` (24 строки) - -Главный шаблон формы, объединяющий все компоненты. - -```django -{% extends "base.html" %} -{% load static %} - -{% block content %} -
-
- {% csrf_token %} - -
- {# Компоненты формы #} - {% include "employees/components/employee_form_personal.html" %} - {% include "employees/components/employee_form_contacts.html" %} - {% include "employees/components/employee_form_position.html" %} - {% include "employees/components/employee_form_employment.html" %} - {% include "employees/components/employee_form_photo.html" %} - {% include "employees/components/employee_form_actions.html" %} -
-
-
- -{# JavaScript инициализация #} -{% include "employees/components/employee_form_scripts.html" %} -{% endblock %} -``` - ---- - -### Компоненты формы - -#### `employee_form_personal.html` - -Личные данные сотрудника. - -**Поля:** -- Фамилия, Имя, Отчество -- Пол -- Дата рождения - -**Особенности:** -- iOS-стайлинг аккордеона -- Валидация обязательных полей -- Автофокус на первое поле - ---- - -#### `employee_form_contacts.html` - -Контактная информация. - -**Поля:** -- Email (корпоративная почта) -- Телефон (рабочий) -- Внутренний номер - -**Особенности:** -- Форматирование телефона -- Валидация email -- Опциональные поля - ---- - -#### `employee_form_position.html` (360 строк) - -Управление должностью сотрудника. - -**Разделы:** -1. Текущая должность (select/input) -2. Создание новой должности (collapse) -3. Редактирование должности (модал) -4. Назначение групп должностей - -**JavaScript:** -- `positionManager.js` - CRUD должностей -- `groupPickers.js` - выбор групп - -**Особенности:** -- Динамические модалы -- AJAX операции -- Валидация прав доступа - ---- - -#### `employee_form_employment.html` - -Информация о трудоустройстве. - -**Поля:** -- Дата приёма на работу -- Статус (работает/уволен) -- Дата увольнения - -**Особенности:** -- Datepicker интеграция -- Условное отображение (статус) -- Автозаполнение текущей даты - ---- - -#### `employee_form_photo.html` - -Загрузка фотографии. - -**Поля:** -- Превью текущей фотографии -- File input для загрузки -- Кнопка удаления фото - -**Особенности:** -- Предпросмотр перед загрузкой -- Валидация размера/формата -- Crop функционал (опционально) - ---- - -#### `employee_form_actions.html` - -Кнопки действий формы. - -**Кнопки:** -- Сохранить изменения -- Отмена (возврат назад) -- Удалить сотрудника (для админов) - -**Особенности:** -- Sticky позиционирование на мобильных -- Подтверждение удаления -- Отслеживание изменений - ---- - -#### `employee_form_scripts.html` (22 строки) - -JavaScript инициализация для формы. - -```django -{% load static %} - -``` - ---- - -### Детали сотрудника - -#### `employee_detail.html` (432 строки) - -Страница просмотра профиля сотрудника. - -**Разделы:** -1. Шапка с фото и основной информацией -2. Контактная информация -3. Должность и отделы -4. Навыки (skills) -5. История изменений -6. Форма редактирования (collapse) - -**JavaScript:** -```django -{% include "employees/components/employee_detail_scripts.html" %} -``` - -#### `employee_detail_scripts.html` (47 строк) - -```django -{% load static %} - -``` - ---- - -## 📅 Компоненты календаря - -### `rightbar_calendar.html` (17 строк) - -Основной файл правой панели с календарём. - -```django -{% load static %} -{# Правая колонка с календарём - рефакторированная версия #} - - - -{# Десктопная версия #} -{% include "includes/components/calendar_desktop.html" %} - -{# Мобильная версия #} -{% include "includes/components/calendar_mobile.html" %} - -{# Модальные окна #} -{% include "includes/components/calendar_modal_create.html" %} -{% include "includes/components/calendar_modal_details.html" %} - -{# JavaScript инициализация #} -{% include "includes/components/calendar_scripts.html" %} -``` - ---- - -### `calendar_desktop.html` (40 строк) - -Десктопная версия календаря в правой панели. - -**Элементы:** -- Заголовок "События" -- Контейнер для FullCalendar -- Кнопка создания события -- Список предстоящих событий - -**Классы:** -- `.rightbar-calendar` - основной контейнер -- `.calendar-widget` - виджет календаря -- `.event-list` - список событий - ---- - -### `calendar_mobile.html` (23 строки) - -Мобильная offcanvas версия календаря. - -**Структура:** -```html -
-
-
События
- -
-
- -
-
-``` - ---- - -### `calendar_modal_create.html` (132 строки) - -Модал для создания нового события. - -**Поля формы:** -- Название события -- Описание -- Дата начала -- Дата окончания -- Цвет события -- Тип (личное/рабочее) - -**Особенности:** -- Валидация дат -- Color picker -- AJAX отправка - ---- - -### `calendar_modal_details.html` (33 строки) - -Модал для просмотра деталей события. - -**Отображаемая информация:** -- Название -- Описание -- Дата и время -- Участники -- Кнопки: Редактировать, Удалить - ---- - -### `calendar_scripts.html` (11 строк) - -Инициализация виджета календаря. - -```django -{% load static %} - -``` - ---- - -## 🏢 Компоненты отделов - -### `department_detail.html` (46 строк) - -Страница деталей отдела. - -```django -{% extends "base.html" %} -{% load static %} - -{% block content %} -
-
- {# Заголовок отдела #} - {% include "departments/components/department_header.html" %} - - {# Колесо команды #} - {% include "departments/components/department_wheel.html" %} - - {# Список участников #} - {% include "departments/components/department_members.html" %} - - {# Боковая панель с настройками #} - {% include "departments/components/department_sidebar.html" %} -
-
- -{# JavaScript инициализация #} -{% include "employees/components/department_scripts.html" %} -{% endblock %} -``` - ---- - -### `_department_controls.html` (275 строк) - -Управление отделом (настройки, роли, права). - -**Разделы:** -1. Выбор руководителя отдела (headPicker) -2. Управление ролями отдела (roleManager) -3. Создание/редактирование ролей -4. Назначение прав (permissions) - -**JavaScript:** -```django -{% include "employees/components/department_controls_scripts.html" %} -``` - -#### `department_controls_scripts.html` (19 строк) - -```django -{% load static %} - -``` - ---- - -## ✅ Best Practices - -### 1. Структура компонентов - -``` -✅ ХОРОШО: Малый размер, одна ответственность -employees/components/ -├── employee_form_personal.html # ~80 строк -├── employee_form_contacts.html # ~60 строк -└── employee_form_scripts.html # ~22 строки - -❌ ПЛОХО: Монолитный файл -employees/ -└── employee_edit.html # 1391 строка! -``` - -### 2. Именование файлов - -``` -✅ ХОРОШО: Префиксы, понятные имена -calendar_modal_create.html -employee_form_personal.html -department_controls_scripts.html - -❌ ПЛОХО: Неясные имена -modal.html -form.html -scripts.html -``` - -### 3. Подключение компонентов - -```django -✅ ХОРОШО: Явное включение с комментариями -{# Личные данные #} -{% include "employees/components/employee_form_personal.html" %} - -{# Контакты #} -{% include "employees/components/employee_form_contacts.html" %} - -❌ ПЛОХО: Неясный порядок без комментариев -{% include "comp1.html" %} -{% include "comp2.html" %} -``` - -### 4. Разделение JavaScript - -```django -✅ ХОРОШО: Отдельный файл инициализации -{% include "employees/components/employee_form_scripts.html" %} - -❌ ПЛОХО: Встроенный -``` - -### 5. Передача контекста - -```django -✅ ХОРОШО: Явная передача параметров -{% include "component.html" with employee=emp can_edit=True %} - -❌ ПЛОХО: Неявный контекст -{% include "component.html" %} -{# Компонент зависит от глобальных переменных #} -``` - -### 6. Блоки и наследование - -```django -✅ ХОРОШО: Расширение базового шаблона -{% extends "base.html" %} -{% block content %} - -{% endblock %} - -❌ ПЛОХО: Дублирование структуры - - - - -``` - -### 7. CSS подключение - -```django -✅ ХОРОШО: Модульные стили в extra_css -{% block extra_css %} - -{% endblock %} - -❌ ПЛОХО: Inline стили -
...
-``` - -### 8. Условное включение - -```django -✅ ХОРОШО: Проверка прав перед включением -{% if user.is_authenticated %} - {% include "includes/rightbar_calendar.html" %} -{% endif %} - -❌ ПЛОХО: Проверка внутри компонента -{# В компоненте: #} -{% if user.is_authenticated %} - -{% endif %} -``` - ---- - -## 🔧 Отладка шаблонов - -### Вывод отладочной информации - -```django -{# Проверка контекста #} -{% if debug %} -
{{ employee|pprint }}
-{% endif %} - -{# Отладка переменных #} -{{ variable|default:"NOT SET" }} -``` - -### Комментарии для разработчиков - -```django -{# === СЕКЦИЯ: Личные данные === #} -{# TODO: Добавить валидацию email #} -{# FIXME: Баг с датой рождения #} -{# NOTE: Этот компонент используется в 3 местах #} -``` - ---- - -## 📞 Поддержка - -При создании новых шаблонов: -1. Следуйте структуре существующих компонентов -2. Используйте префиксы в именах файлов -3. Документируйте сложную логику -4. Разделяйте HTML и JavaScript -5. Тестируйте на разных экранах - ---- - -**Дата актуализации**: 4 ноября 2025 г. -**Версия**: 1.0 diff --git a/backend/docs/TEXTAREA_AUTO_EXPAND.md b/backend/docs/TEXTAREA_AUTO_EXPAND.md deleted file mode 100644 index 2844dd40..00000000 --- a/backend/docs/TEXTAREA_AUTO_EXPAND.md +++ /dev/null @@ -1,182 +0,0 @@ -# Унификация автоматического расширения textarea - -## Описание изменений - -Создан универсальный модуль `textareaAutoExpand.js`, который автоматически обрабатывает расширение всех textarea с `rows="1"` в формах комментариев и сообщений по всему приложению. - -## Реализация - -### Новый модуль: textareaAutoExpand.js - -**Путь:** `backend/static/js/components/textareaAutoExpand.js` - -**Функциональность:** -- Автоматически находит все textarea с атрибутом `rows="1"` или классом `composer-textarea` -- Расширяет textarea при вводе текста до максимум 6 строк (144px) -- Использует MutationObserver для отслеживания динамически добавляемых textarea -- Работает со всеми формами комментариев: лента, заявки, модальные окна, чат - -**Принцип работы:** -```javascript -// Сбрасываем высоту для корректного вычисления scrollHeight -textarea.style.height = 'auto'; - -// Устанавливаем новую высоту, но не больше максимальной (6 строк) -const newHeight = Math.min(textarea.scrollHeight, MAX_HEIGHT); -textarea.style.height = newHeight + 'px'; -``` - -### Поддерживаемые селекторы - -Модуль автоматически работает с: -- `.comment-form-quick textarea` - быстрые комментарии в ленте -- `.comment-form-modal textarea` - комментарии в модальном окне -- `.request-comment-form textarea` - комментарии к заявкам -- `.comment-new textarea` - новые комментарии в списке заявок -- `.composer-textarea` - поле ввода в чате -- `.message-input` - общий класс для всех полей ввода - -### Условия применения - -Авторасширение применяется **только** к textarea, которые удовлетворяют условию: -- Имеют атрибут `rows="1"` **ИЛИ** -- Имеют класс `composer-textarea` - -Это означает, что textarea в модальных окнах редактирования (с `rows="3"` или `rows="4"`) **не будут** автоматически расширяться. - -## Удалённые файлы - -### 1. textareaAutogrowHandler.js -- **Причина удаления:** Использовался только в `post_form.html`, функциональность заменена универсальным модулем -- **Зависимости:** Удалён импорт из `backend/templates/feed/post_form.html` - -### 2. textareaAutogrow.js -- **Причина удаления:** Требовал явного атрибута `data-autogrow`, менее универсален -- **Зависимости:** Удалён импорт из `backend/templates/base.html` - -## Изменённые файлы - -### 1. backend/templates/base.html -```diff -- import { initTextareaAutogrow } from '{% static "js/components/textareaAutogrow.js" %}'; -+ {# Универсальный модуль для автоматического расширения textarea #} -+ -``` - -Удалена инициализация: -```diff -- const textareaAutogrow = initTextareaAutogrow(); -- if (textareaAutogrow) window.textareaAutogrow = textareaAutogrow; -``` - -### 2. backend/templates/feed/post_form.html -```diff -- import { initTextareaAutogrow } from "{% static 'js/components/textareaAutogrowHandler.js' %}"; -- -- // Автоматическое изменение размера textarea -- initTextareaAutogrow({ selector: 'form.card textarea' }); -``` - -### 3. backend/static/js/components/chatMarkRead.js -```diff - function autosize() { - if (!ta) return; -- ta.style.height = 'auto'; -- ta.style.height = Math.min(ta.scrollHeight, 6 * 24) + 'px'; -+ // Авторасширение теперь обрабатывается textareaAutoExpand.js -+ // Оставляем только autoscroll логику - if (atBottom()) autoscroll(); - } -``` - -### 4. backend/static/js/components/requestCommentsHandler.js -Удалена функция `initTextareaAutosize()` и её вызов: -```diff -- function initTextareaAutosize() { -- // ... закомментированный код -- } -- -- document.addEventListener('DOMContentLoaded', () => { -- autoloadAllCounts(); -- initTextareaAutosize(); -- }); -+ document.addEventListener('DOMContentLoaded', autoloadAllCounts); -``` - -### 5. backend/static/scss/components/_message-inputs.scss -Удалены конфликтующие `min-height` и `max-height`, которые мешали автоматическому расширению: -```diff - .message-input, - .composer-textarea { - flex: 1 1 auto; -- min-height: 44px; -- max-height: 260px; - border: none !important; - ... - } - - .message-field--compact .message-input { -- min-height: 32px; - font-size: 0.95rem; -+ // min-height удален - управляется textareaAutoExpand.js - } -``` - -### 6. backend/static/css/components/request-list.css -Удалены специфичные стили для `.comment-new textarea`, которые перебивали универсальное поведение: -```diff -- /* ───────────────────────────────────────────────────────────── -- COMMENT TEXTAREA -- ────────────────────────────────────────────────────────────── */ -- .comment-new textarea { -- resize: vertical; -- max-height: 220px; -- min-height: 44px; -- overflow-y: auto; -- } -``` - -## Преимущества новой реализации - -1. **Единообразие:** Все textarea с `rows="1"` работают одинаково по всему приложению -2. **Автоматичность:** Не требует явной инициализации в каждом модуле -3. **Динамичность:** Автоматически обрабатывает textarea, добавленные через AJAX/модальные окна -4. **Простота поддержки:** Один файл вместо трёх разных реализаций -5. **Производительность:** Единая инициализация вместо множественных обработчиков - -## Тестирование - -Необходимо протестировать автоматическое расширение в: -- ✅ Быстрые комментарии в ленте (feed) -- ✅ Комментарии в модальном окне поста -- ✅ Комментарии к заявкам (request_detail.html) -- ✅ Комментарии в обработке заявок (request_process.html) -- ✅ Комментарии в списке заявок (request_list_full.html) -- ✅ Поле ввода в чате (composer-textarea) - -**Ожидаемое поведение:** -- Начальная высота: 1 строка (~38px) -- Максимальная высота: 6 строк (144px) -- После 6 строк появляется скроллбар -- При удалении текста высота уменьшается - -## Обратная совместимость - -Модуль экспортирует API в `window.textareaAutoExpand`: -```javascript -window.textareaAutoExpand = { - init: initAllTextareas, // Пересканировать все textarea - initTextarea: initAutoExpand, // Инициализировать конкретный textarea - autoExpand: autoExpand // Применить расширение к textarea -}; -``` - -Это позволяет вручную инициализировать textarea, если требуется: -```javascript -// Пример использования -const textarea = document.querySelector('#myTextarea'); -window.textareaAutoExpand.initTextarea(textarea); -``` - -## Дата изменения -2025-01-XX diff --git a/backend/docs/TEXTAREA_TESTING.md b/backend/docs/TEXTAREA_TESTING.md deleted file mode 100644 index 58dca283..00000000 --- a/backend/docs/TEXTAREA_TESTING.md +++ /dev/null @@ -1,121 +0,0 @@ -# 🎯 Тестирование унификации textarea - -## Что было исправлено - -**Проблема:** Поля ввода комментариев в заявлениях вели себя иначе, чем в новостях. - -**Причина:** -1. В `request-list.css` были специфичные стили для `.comment-new textarea`: - - `min-height: 44px` - - `max-height: 220px` - - `resize: vertical` - -2. В `_message-inputs.scss` были конфликтующие стили: - - `.message-input { min-height: 44px; max-height: 260px; }` - - `.message-field--compact .message-input { min-height: 32px; }` - -**Решение:** -- ✅ Удалены все фиксированные `min-height` и `max-height` из CSS -- ✅ Удален `resize: vertical` (теперь только `resize: none`) -- ✅ Все textarea с `rows="1"` автоматически управляются через `textareaAutoExpand.js` - -## Как тестировать - -### 1. Комментарии в ленте новостей -1. Откройте главную страницу `/feed/` -2. Найдите любую новость -3. Начните вводить комментарий в поле "Оставить комментарий…" -4. ✅ **Ожидается:** Поле плавно расширяется от 1 до 6 строк (144px) - -### 2. Комментарии в заявлениях -1. Откройте список заявлений `/requests/` -2. Нажмите на кнопку "Комментарии" под любой заявкой -3. Начните вводить комментарий в поле "Написать…" -4. ✅ **Ожидается:** Поле ведёт себя **ИДЕНТИЧНО** полю в новостях - -### 3. Комментарии в детальном просмотре заявки -1. Откройте заявку `/requests//` -2. Прокрутите до раздела комментариев -3. Начните вводить комментарий -4. ✅ **Ожидается:** Точно такое же поведение, как в новостях - -### 4. Комментарии при обработке заявки -1. Откройте заявку для обработки `/requests//process/` -2. Прокрутите до комментариев -3. Начните вводить -4. ✅ **Ожидается:** Единообразное поведение - -### 5. Модальные окна -1. Откройте модальное окно новости -2. Попробуйте добавить комментарий -3. ✅ **Ожидается:** Расширение от 1 до 6 строк - -## Что проверять - -| Критерий | Ожидаемое поведение | -|----------|-------------------| -| **Начальная высота** | 1 строка (~38-40px) | -| **Максимальная высота** | 6 строк (144px) | -| **Поведение при вводе** | Плавное автоматическое расширение | -| **Скроллбар** | Появляется только после 6 строк | -| **Уменьшение высоты** | При удалении текста высота уменьшается | -| **Единообразие** | Все поля с `rows="1"` работают одинаково | -| **Визуальный стиль** | Нет отличий между новостями и заявлениями | - -## Известные исключения - -Следующие textarea **НЕ должны** автоматически расширяться: -- Модальные окна редактирования с `rows="3"` или `rows="4"` -- Поле создания новости с большим textarea -- Любые textarea без `rows="1"` и без класса `.composer-textarea` - -## Проверка в DevTools - -Откройте DevTools → Elements и проверьте textarea: - -### ✅ Правильно (новости и заявления должны быть одинаковыми): -```html -