# Руководство по шаблонам EUSRR
## Статус рефакторинга
**Версия**: 2.1
**Дата обновления**: январь 2025
**Завершено**: Фазы 0-5 (85%)
---
## Архитектура шаблонов
### Базовая структура
```
templates/
├── base.html # Корневой шаблон с navbar/sidebar
├── includes/ # Общие компоненты
│ ├── navbar.html # Верхняя навигация
│ ├── sidebar.html # Боковое меню
│ ├── rightbar_calendar.html # Календарь (237 строк, было 996)
│ └── messages.html # Django messages
│
├── admin/ # Административные override
├── auth/ # Авторизация, регистрация
├── communications/ # Чаты
├── documents/ # Документы
├── emails/ # Email-шаблоны
├── employees/ # Сотрудники, отделы
│ ├── components/ # Переиспользуемые компоненты
│ │ ├── department_controls.html
│ │ ├── department_info.html
│ │ ├── department_modals.html
│ │ └── department_sidebar.html
│ ├── _employee_edit.html # 24 строки (было 1391)
│ ├── department_detail.html # 46 строк (было 1025)
│ ├── employees_list.html
│ └── ...
│
├── feed/ # Лента новостей
├── requests_app/ # Заявления сотрудников
└── search/ # Поиск
```
---
## Статические файлы
### JavaScript
**Утилиты** (`static/js/utils/`):
- `stringUtils.js` (115 строк) - esc(), norm(), escAttr(), truncate(), getInitials()
- `timing.js` (173 строки) - debounce(), throttle(), delay(), poll(), once()
- `scroll.js` (205 строк) - smoothScrollTo(), scrollToTop(), isElementInViewport()
- `index.js` - Центральная точка экспорта
**Компоненты** (`static/js/components/`):
- `listFilter.js` (279 строк) - Универсальная фильтрация списков
- `teamWheel.js` (308 строк) - Вращающийся виджет команды
- `calendarWidget.js` (1002 строки) - FullCalendar + CRUD событий
- `index.js` - Центральная точка экспорта (v1.1.0)
### CSS
**Компоненты** (`static/css/components/`):
- `chat-detail.css` - Чат
- `department-list.css` - Список отделов
- `department-sidebar.css` - Боковая панель отдела
- `document-list.css` - Список документов
- `employee-detail.css` - Детали сотрудника
- `cards.css` - Карточки в ленте
- `login.css` - Страница входа
- `logout.css` - Страница выхода
- `navbar.css` - Верхняя навигация
- `password-forms.css` - Формы паролей
- `register.css` - Регистрация
- `request-list.css` - Список заявлений
- `rightbar-calendar.css` - Календарь
- `search-results.css` - Результаты поиска
- `sidebar.css` - Боковое меню
- `team-wheel.css` - Виджет команды
- И другие...
**Общие стили**:
- `common.css` (245 строк) - Переиспользуемые классы
- `variables.css` - CSS-переменные и цвета
---
## Использование компонентов
### JavaScript утилиты
#### В base.html (глобальный импорт)
```django
```
#### В шаблонах
```django
{% block extra_js %}
{% endblock %}
```
### CSS компоненты
#### Подключение в шаблоне
```django
{% block extra_css %}
{% endblock %}
```
#### Использование общих классов (common.css)
```html
Удалить сотрудника?
Это действие необратимо
```
---
## Именование файлов
### Шаблоны
- `{model}_list.html` - Списки (employees_list, document_list)
- `{model}_detail.html` - Детальные страницы (employee_detail, department_detail)
- `{model}_form.html` - Формы создания/редактирования
- `{model}_confirm_delete.html` - Подтверждение удаления
- `_{partial}.html` - Партиалы (начинаются с `_`)
### Компоненты
- `components/{name}.html` - Переиспользуемые компоненты приложения
- `includes/{name}.html` - Глобальные компоненты
---
## Лучшие практики
### 1. Структура шаблона
```django
{% extends "base.html" %}
{% load static %}
{% block title %}Название страницы{% endblock %}
{% block extra_css %}
{% endblock %}
{% block content %}
{# Основной контент #}
{% endblock %}
{% block extra_js %}
{% endblock %}
```
### 2. Использование компонентов
```django
{# С параметрами #}
{% include "employees/components/department_info.html" with department=dept %}
{# С контекстом #}
{% with user=employee %}
{% include "includes/components/user_card.html" %}
{% endwith %}
```
### 3. Стили и скрипты
✅ **ПРАВИЛЬНО**:
```django
{# Стили в extra_css блоке #}
{% block extra_css %}
{% endblock %}
{# Скрипты в extra_js блоке #}
{% block extra_js %}
{% endblock %}
```
❌ **НЕПРАВИЛЬНО**:
```django
{# Встроенные стили в body #}
{# Встроенные скрипты в body #}
```
### 4. Безопасность
```django
{# Всегда используйте CSRF-защиту #}
{# Экранирование HTML (по умолчанию включено) #}
{{ user.bio }} {# Безопасно #}
{# Если нужен HTML (используйте с осторожностью!) #}
{{ user.bio|safe }} {# Только для доверенного контента! #}
```
---
## Компоненты и их использование
### ListFilter (универсальная фильтрация)
**Импорт**:
```javascript
import { ListFilter, createDataAttrMatcher, createSelectorMatcher } from '{% static "js/components/listFilter.js" %}';
```
**Базовое использование**:
```javascript
new ListFilter({
listSelector: '.employees-list',
itemSelector: '.employee-card',
searchInputSelector: '#searchInput'
});
```
**С пользовательской функцией поиска**:
```javascript
new ListFilter({
listSelector: '#empList',
itemSelector: '.emp-row',
searchInputSelector: '#empFilter',
matchFn: createDataAttrMatcher(['name', 'email', 'department']),
clearButtonSelector: '#clearBtn',
emptyMessageSelector: '.no-results',
debounceMs: 300
});
```
### TeamWheel (вращающийся виджет команды)
**Импорт**:
```javascript
import { initTeamWheel } from '{% static "js/components/teamWheel.js" %}';
```
**Использование**:
```javascript
initTeamWheel({
wheelId: 'teamWheel',
columns: 4,
speed: 30,
pauseOnHover: true
});
```
### CalendarWidget (календарь событий)
**Импорт**:
```javascript
import { initCalendarWidget } from '{% static "js/components/calendarWidget.js" %}';
```
**Использование**:
```javascript
window.calendarWidget = initCalendarWidget({
deskContainerId: 'calendarRight',
mobContainerId: 'calendarRightMobile',
apiEventsUrl: '/api/v1/calendar/events/',
apiMyDeptsUrl: '/api/v1/departments/my-departments/',
defaultColor: '#0d6efd'
});
```
**API виджета**:
```javascript
// Обновить списки недели
window.calendarWidget.updateWeekLists();
// Перезагрузить события
window.calendarWidget.refetchEvents();
// Получить список отделов
const departments = window.calendarWidget.getDepartments();
// Получить текущее состояние
const state = window.calendarWidget.getState(); // { type: 'company'|'dept', deptId: number|null }
```
---
## Отладка и тестирование
### Проверка подключения модулей
```javascript
// В консоли браузера
console.log(window.esc); // function esc(str) { ... }
console.log(window.debounce); // function debounce(fn, ms) { ... }
```
### Проверка работы компонентов
```javascript
// ListFilter
const filter = new ListFilter({ /* ... */ });
filter.filter('test'); // Применить фильтр
filter.refresh(); // Обновить список элементов
filter.clear(); // Очистить фильтр
// CalendarWidget
window.calendarWidget.refetchEvents(); // Перезагрузить события
```
### Django тесты
```python
from django.test import TestCase
from django.template.loader import get_template
class TemplateRenderingTests(TestCase):
def test_employee_list_renders(self):
template = get_template('employees/employees_list.html')
self.assertIsNotNone(template)
def test_no_inline_styles(self):
"""Проверка отсутствия встроенных стилей"""
template = get_template('employees/employees_list.html')
content = template.render({})
self.assertNotIn('