# Архитектура Team Planner
---
## 1. C4 Model
### 1.1 Level 1: System Context
```mermaid
flowchart TB
subgraph external [" "]
User["👤 Пользователь
Член команды разработки"]
AI["🤖 AI Proxy Service
LLM для оценки задач"]
KC["🔐 Keycloak
auth.vigdorov.ru
Identity Provider"]
end
subgraph system ["Team Planner"]
TP["🎯 Team Planner
Приложение для управления
бэклогом идей команды"]
end
User -->|"Управляет идеями,
командой, комментариями
[HTTPS]"| TP
User -->|"Авторизация
[OIDC/PKCE]"| KC
KC -->|"JWT токены"| TP
TP -->|"Запросы на оценку
трудозатрат
[HTTPS/REST]"| AI
style TP fill:#1168bd,color:#fff
style User fill:#08427b,color:#fff
style AI fill:#999,color:#fff
style KC fill:#c92a2a,color:#fff
```
### 1.2 Level 2: Container Diagram
```mermaid
flowchart TB
User["👤 Пользователь
Член команды разработки"]
KC["🔐 Keycloak
auth.vigdorov.ru"]
subgraph TeamPlanner ["Team Planner"]
SPA["📱 Frontend SPA
React, TypeScript, MUI
Веб-интерфейс для
работы с идеями"]
API["⚙️ Backend API
NestJS, TypeScript
REST API + WebSocket"]
DB[("🗄️ Database
PostgreSQL
Хранение идей,
команды, комментариев")]
end
AI["🤖 AI Proxy Service
LLM для оценки задач"]
User -->|"Использует
[HTTPS]"| SPA
User <-->|"OIDC Login
[Redirect]"| KC
SPA -->|"API запросы
[REST + Bearer JWT]"| API
API -.->|"Валидация JWT
[JWKS]"| KC
API -->|"Читает/пишет
[TypeORM]"| DB
API -->|"Оценка трудозатрат
[HTTPS/REST]"| AI
style SPA fill:#438dd5,color:#fff
style API fill:#438dd5,color:#fff
style DB fill:#438dd5,color:#fff
style User fill:#08427b,color:#fff
style AI fill:#999,color:#fff
style KC fill:#c92a2a,color:#fff
```
---
## 2. Sequence Diagrams
### 2.0 Авторизация (Keycloak OIDC)
```mermaid
sequenceDiagram
autonumber
actor User as Пользователь
participant FE as Frontend
(React)
participant KC as Keycloak
(auth.vigdorov.ru)
participant BE as Backend
(NestJS)
User->>FE: Открывает приложение
FE->>FE: keycloak.init({ onLoad: 'login-required' })
FE->>KC: Redirect на /auth (PKCE)
KC-->>User: Форма входа
User->>KC: Вводит логин/пароль
KC->>KC: Проверяет credentials
KC-->>FE: Redirect с authorization code
FE->>KC: POST /token (code + code_verifier)
KC-->>FE: { access_token, refresh_token }
FE->>FE: Сохраняет токены в памяти
FE-->>User: Показывает приложение
Note over FE,BE: Все последующие API запросы
FE->>BE: GET /api/ideas
Authorization: Bearer {token}
BE->>KC: GET /certs (JWKS, кэшируется)
KC-->>BE: Public keys
BE->>BE: Валидация JWT подписи
BE-->>FE: 200 OK { data }
Note over FE,KC: Автообновление токена (каждые 10 сек)
FE->>KC: POST /token (refresh_token)
KC-->>FE: { new_access_token }
```
### 2.1 Создание идеи
```mermaid
sequenceDiagram
autonumber
actor User as Пользователь
participant FE as Frontend
(React)
participant BE as Backend
(NestJS)
participant DB as PostgreSQL
User->>FE: Заполняет форму идеи
FE->>FE: Валидация на клиенте
FE->>BE: POST /api/ideas
BE->>BE: Валидация DTO
BE->>DB: INSERT INTO ideas
DB-->>BE: idea record
BE-->>FE: 201 Created { idea }
FE->>FE: Добавляет в store
FE-->>User: Показывает новую идею в списке
```
### 2.2 Inline-редактирование идеи
```mermaid
sequenceDiagram
autonumber
actor User as Пользователь
participant FE as Frontend
(React)
participant BE as Backend
(NestJS)
participant DB as PostgreSQL
User->>FE: Double-click на ячейку
FE->>FE: Переключает в режим редактирования
User->>FE: Изменяет значение, blur/Enter
FE->>FE: Оптимистичное обновление store
FE->>BE: PATCH /api/ideas/:id
BE->>BE: Валидация DTO
BE->>DB: UPDATE ideas SET ...
DB-->>BE: updated idea
BE-->>FE: 200 OK { idea }
FE->>FE: Подтверждает изменение в store
alt Ошибка
BE-->>FE: 4xx/5xx error
FE->>FE: Откат оптимистичного обновления
FE-->>User: Показывает ошибку
end
```
### 2.3 Drag & Drop (изменение порядка)
```mermaid
sequenceDiagram
autonumber
actor User as Пользователь
participant FE as Frontend
(React)
participant BE as Backend
(NestJS)
participant DB as PostgreSQL
User->>FE: Перетаскивает идею
FE->>FE: dnd-kit обновляет UI
FE->>FE: Оптимистичное обновление порядка
FE->>BE: PATCH /api/ideas/reorder
Note right of FE: { ids: [id1, id2, ...] }
BE->>DB: UPDATE ideas SET position = ...
DB-->>BE: OK
BE-->>FE: 200 OK
FE-->>User: Порядок сохранён
```
### 2.4 AI-оценка трудозатрат
```mermaid
sequenceDiagram
autonumber
actor User as Пользователь
participant FE as Frontend
(React)
participant BE as Backend
(NestJS)
participant AI as AI Proxy
participant DB as PostgreSQL
User->>FE: Нажимает "Оценить"
FE->>FE: Показывает loader
FE->>BE: POST /api/ai/estimate
Note right of FE: { ideaId }
BE->>DB: SELECT idea, team_members
DB-->>BE: idea + team data
BE->>BE: Формирует промпт
BE->>AI: POST /chat/completions
Note right of BE: prompt с описанием идеи
и составом команды
AI-->>BE: LLM response
BE->>BE: Парсит ответ
BE->>DB: UPDATE ideas SET estimate = ...
DB-->>BE: OK
BE-->>FE: 200 OK { estimate }
FE->>FE: Обновляет store
FE-->>User: Показывает оценку
```
### 2.5 Добавление комментария
```mermaid
sequenceDiagram
autonumber
actor User as Пользователь
participant FE as Frontend
(React)
participant BE as Backend
(NestJS)
participant DB as PostgreSQL
User->>FE: Пишет комментарий
FE->>BE: POST /api/ideas/:id/comments
Note right of FE: { text, parentId? }
BE->>BE: Валидация
BE->>DB: INSERT INTO comments
DB-->>BE: comment record
BE-->>FE: 201 Created { comment }
FE->>FE: Добавляет в store
FE-->>User: Показывает комментарий
```
### 2.6 Загрузка списка идей с фильтрами
```mermaid
sequenceDiagram
autonumber
actor User as Пользователь
participant FE as Frontend
(React)
participant BE as Backend
(NestJS)
participant DB as PostgreSQL
User->>FE: Открывает страницу / меняет фильтры
FE->>FE: Показывает skeleton loader
FE->>BE: GET /api/ideas?status=...&priority=...
BE->>DB: SELECT * FROM ideas WHERE ... ORDER BY ...
DB-->>BE: ideas[]
BE-->>FE: 200 OK { data, total, page }
FE->>FE: Сохраняет в store
FE-->>User: Отображает таблицу
```
---
## 3. API Contracts
### 3.1 Ideas
#### GET /api/ideas
Получение списка идей с пагинацией и фильтрами.
**Query Parameters:**
```typescript
{
page?: number; // default: 1
limit?: number; // default: 50
status?: IdeaStatus; // фильтр по статусу
priority?: Priority; // фильтр по приоритету
module?: Module; // фильтр по модулю
color?: string; // фильтр по цвету
search?: string; // поиск по тексту
sortBy?: string; // поле для сортировки
sortOrder?: 'asc' | 'desc';
}
```
**Response 200:**
```typescript
{
data: Idea[];
meta: {
total: number;
page: number;
limit: number;
totalPages: number;
}
}
```
#### POST /api/ideas
Создание новой идеи.
**Request Body:**
```typescript
{
title: string; // обязательное
description?: string;
status: IdeaStatus; // default: 'new'
priority: Priority; // default: 'medium'
module: Module[];
targetAudience?: string;
painPoint?: string;
aiRole?: string;
validationMethod?: string;
color?: string;
position?: number;
}
```
**Response 201:**
```typescript
{
id: string;
title: string;
description: string | null;
status: IdeaStatus;
priority: Priority;
module: Module[];
targetAudience: string | null;
painPoint: string | null;
aiRole: string | null;
validationMethod: string | null;
color: string | null;
position: number;
estimate: Estimate | null;
createdAt: string;
updatedAt: string;
}
```
#### PATCH /api/ideas/:id
Обновление идеи (partial update).
**Request Body:**
```typescript
Partial<{
title: string;
description: string;
status: IdeaStatus;
priority: Priority;
module: Module[];
targetAudience: string;
painPoint: string;
aiRole: string;
validationMethod: string;
color: string;
}>
```
**Response 200:** `Idea`
#### DELETE /api/ideas/:id
Удаление идеи.
**Response 204:** No Content
#### PATCH /api/ideas/reorder
Изменение порядка идей.
**Request Body:**
```typescript
{
ids: string[]; // ID идей в новом порядке
}
```
**Response 200:**
```typescript
{
success: true;
}
```
### 3.2 Comments
#### GET /api/ideas/:ideaId/comments
Получение комментариев к идее.
**Response 200:**
```typescript
{
data: Comment[];
}
// Comment
{
id: string;
text: string;
ideaId: string;
parentId: string | null; // для тредов
createdAt: string;
updatedAt: string;
replies?: Comment[]; // вложенные ответы
}
```
#### POST /api/ideas/:ideaId/comments
Добавление комментария.
**Request Body:**
```typescript
{
text: string;
parentId?: string; // для ответа на комментарий
}
```
**Response 201:** `Comment`
#### DELETE /api/comments/:id
Удаление комментария.
**Response 204:** No Content
### 3.3 Team
#### GET /api/team/members
Получение списка членов команды.
**Response 200:**
```typescript
{
data: TeamMember[];
}
// TeamMember
{
id: string;
name: string;
role: TeamRole;
productivity: {
trivial: number; // часы
easy: number;
medium: number;
hard: number;
epic: number;
};
createdAt: string;
updatedAt: string;
}
```
#### POST /api/team/members
Добавление члена команды.
**Request Body:**
```typescript
{
name: string;
role: TeamRole;
productivity?: {
trivial?: number;
easy?: number;
medium?: number;
hard?: number;
epic?: number;
};
}
```
**Response 201:** `TeamMember`
#### PATCH /api/team/members/:id
Обновление члена команды.
**Request Body:** `Partial`
**Response 200:** `TeamMember`
#### DELETE /api/team/members/:id
Удаление члена команды.
**Response 204:** No Content
#### GET /api/team/summary
Сводка по команде.
**Response 200:**
```typescript
{
totalMembers: number;
byRole: Record;
}
```
### 3.4 AI
#### POST /api/ai/estimate
AI-оценка трудозатрат для идеи.
**Request Body:**
```typescript
{
ideaId: string;
}
```
**Response 200:**
```typescript
{
ideaId: string;
estimate: {
totalHours: number;
totalDays: number;
complexity: 'trivial' | 'easy' | 'medium' | 'hard' | 'epic';
breakdown: {
role: TeamRole;
hours: number;
complexity: Complexity;
description: string;
}[];
recommendations?: string[];
};
}
```
### 3.5 Enums
```typescript
enum IdeaStatus {
NEW = 'new',
DISCUSSING = 'discussing',
APPROVED = 'approved',
IN_PROGRESS = 'in_progress',
DONE = 'done',
REJECTED = 'rejected'
}
enum Priority {
CRITICAL = 'critical',
HIGH = 'high',
MEDIUM = 'medium',
LOW = 'low'
}
enum Module {
FRONTEND = 'frontend',
BACKEND = 'backend',
AI = 'ai',
MOBILE = 'mobile',
INFRASTRUCTURE = 'infrastructure',
OTHER = 'other'
}
enum TeamRole {
BACKEND = 'backend',
FRONTEND = 'frontend',
AI_ML = 'ai_ml',
ANALYST = 'analyst',
QA = 'qa',
DEVOPS = 'devops',
DESIGNER = 'designer',
PM = 'pm'
}
enum Complexity {
TRIVIAL = 'trivial',
EASY = 'easy',
MEDIUM = 'medium',
HARD = 'hard',
EPIC = 'epic'
}
```
---
## 4. UI Prototypes
### 4.1 Главная страница - Список идей
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 🎯 Team Planner [Команда] [+ Идея] │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─ Фильтры ─────────────────────────────────────────────────────────────┐ │
│ │ [Статус ▼] [Приоритет ▼] [Модуль ▼] [Цвет ▼] [🔍 Поиск... ] │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Таблица идей ────────────────────────────────────────────────────────┐ │
│ │ ⋮⋮ │ Статус │ ⚡ │ Модуль │ Идея │ Для кого │ Оценка │ │
│ ├────┼───────────┼────┼──────────┼────────────────┼──────────┼─────────┤ │
│ │ ⋮⋮ │ 🟢 Новая │ 🔴 │ Frontend │ Добавить темну │ Все поль │ 3д │ │
│ │ ⋮⋮ │ 🟡 В обсу │ 🟡 │ Backend │ API кэширован │ Разработ │ 5д │ │
│ │ ⋮⋮ │ 🔵 Одобре │ 🟢 │ AI │ Автоматическа │ Менеджер │ 10д │ │
│ │ ⋮⋮ │ 🟣 В рабо │ 🔴 │ Backend │ Оптимизация з │ Все │ 7д │ │
│ │ │ │ │ │ │ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ Показано 4 из 24 [< Пред] 1 2 3 [След >] │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Легенда:
⋮⋮ - drag handle для перетаскивания
⚡ - приоритет (иконка молнии)
🔴🟡🟢 - цветовые индикаторы приоритета
```
### 4.2 Расширенная строка с комментариями
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ ⋮⋮ │ 🟢 Новая │ 🔴 │ Frontend │ Добавить тёмную тему │ Все │ 3д [▼]│
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─ Детали ──────────────────────────────────────────────────────────────┐ │
│ │ Боль: Пользователи жалуются на яркий интерфейс вечером │ │
│ │ AI роль: — │ │
│ │ Проверка: A/B тест с 10% пользователей │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Комментарии (3) ─────────────────────────────────────────────────────┐ │
│ │ 💬 Иван: Нужно согласовать палитру с дизайнером 2ч назад │ │
│ │ └─ 💬 Мария: Уже в процессе, будет готово завтра 1ч назад │ │
│ │ 💬 Петр: Предлагаю использовать CSS variables 30м назад │ │
│ │ │ │
│ │ [Написать комментарий... ] [Отправить] │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ [🤖 Оценить AI] [✏️ Редактировать] [🗑️ Удалить] │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 4.3 Модальное окно создания/редактирования идеи
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Новая идея [✕] │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Суть идеи * │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ ┌──────────────────┐ │
│ │ Статус │ │ Приоритет │ │ Модуль │ │
│ │ [Новая ▼] │ │ [Средний ▼] │ │ [Frontend ▼] │ │
│ └─────────────────────┘ └─────────────────────┘ └──────────────────┘ │
│ │
│ Для кого эта идея │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Какую боль решает │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Роль AI (если применимо) │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Быстрый способ проверить │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ Цвет строки │
│ [⬜][🟥][🟧][🟨][🟩][🟦][🟪] │
│ │
│ [Отмена] [💾 Сохранить] │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 4.4 Страница управления командой
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 🎯 Team Planner [Команда] [+ Идея] │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ◀ Назад к идеям │
│ │
│ ┌─ Состав команды ──────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 👨💻 Backend: 3 👩💻 Frontend: 2 🤖 AI/ML: 1 📊 Аналитик: 1 │ │
│ │ 🧪 QA: 2 🔧 DevOps: 1 🎨 Дизайн: 1 📋 PM: 1 │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Участники ───────────────────────────────────────────────────────────┐ │
│ │ Имя │ Роль │ Trivial │ Easy │ Med │ Hard │ Epic │ │
│ ├──────────────────┼────────────┼─────────┼──────┼─────┼──────┼────────┤ │
│ │ Иван Петров │ Backend │ 1ч │ 4ч │ 16ч │ 40ч │ 80ч │ │
│ │ Мария Сидорова │ Frontend │ 1ч │ 4ч │ 16ч │ 40ч │ 80ч │ │
│ │ Алексей Козлов │ AI/ML │ 2ч │ 8ч │ 24ч │ 60ч │ 120ч │ │
│ │ ... │ │ │ │ │ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ [+ Добавить участника] │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 4.5 Модальное окно AI-оценки
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 🤖 AI-оценка трудозатрат [✕] │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Идея: Добавить тёмную тему │
│ │
│ ┌─ Общая оценка ────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ⏱️ 24 часа (~3 рабочих дня) │ │
│ │ 📊 Сложность: Средняя │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Разбивка по ролям ───────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 👩💻 Frontend │ 16ч │ ████████████████░░░░ │ Средняя │ │
│ │ 🎨 Дизайнер │ 4ч │ ████░░░░░░░░░░░░░░░░ │ Лёгкая │ │
│ │ 🧪 QA │ 4ч │ ████░░░░░░░░░░░░░░░░ │ Лёгкая │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ Рекомендации ────────────────────────────────────────────────────────┐ │
│ │ • Использовать CSS custom properties для цветовой схемы │ │
│ │ • Добавить переключатель в настройки пользователя │ │
│ │ • Учесть системные настройки (prefers-color-scheme) │ │
│ └───────────────────────────────────────────────────────────────────────┘ │
│ │
│ [Сохранить оценку] │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## 5. UI Specification
### 5.1 Цветовая палитра
#### Основные цвета
```
Primary: #1976D2 (MUI Blue 700)
Primary Light: #42A5F5 (MUI Blue 400)
Primary Dark: #1565C0 (MUI Blue 800)
Secondary: #9C27B0 (MUI Purple 500)
Background: #FFFFFF
Surface: #F5F5F5 (Grey 100)
```
#### Статусы идей
```
New: #4CAF50 (Green 500) 🟢
Discussing: #FF9800 (Orange 500) 🟡
Approved: #2196F3 (Blue 500) 🔵
In Progress: #9C27B0 (Purple 500) 🟣
Done: #607D8B (Blue Grey 500) ⚫
Rejected: #F44336 (Red 500) 🔴
```
#### Приоритеты
```
Critical: #D32F2F (Red 700) фон: #FFEBEE
High: #F57C00 (Orange 700) фон: #FFF3E0
Medium: #FBC02D (Yellow 700) фон: #FFFDE7
Low: #388E3C (Green 700) фон: #E8F5E9
```
#### Цвета для маркировки строк
```
Без цвета: transparent
Красный: #FFCDD2 (Red 100)
Оранжевый: #FFE0B2 (Orange 100)
Жёлтый: #FFF9C4 (Yellow 100)
Зелёный: #C8E6C9 (Green 100)
Голубой: #BBDEFB (Blue 100)
Фиолетовый: #E1BEE7 (Purple 100)
```
### 5.2 Типографика
```
Font Family: 'Roboto', 'Helvetica', 'Arial', sans-serif
H1: 24px, weight 500, line-height 1.2
H2: 20px, weight 500, line-height 1.3
H3: 16px, weight 500, line-height 1.4
Body1: 14px, weight 400, line-height 1.5
Body2: 12px, weight 400, line-height 1.43
Caption: 12px, weight 400, line-height 1.66, color: #757575
```
### 5.3 Компоненты
#### Кнопки
| Тип | Использование | Стиль |
|-----|---------------|-------|
| Primary | Главное действие (Сохранить, Создать) | Filled, Primary color |
| Secondary | Вторичные действия (Отмена) | Outlined, Grey |
| Text | Третичные действия (Назад) | Text only |
| Icon | Действия в строке таблицы | IconButton, 40x40px |
| Danger | Удаление | Filled, Red |
```
Border Radius: 4px
Height: 36px (medium), 40px (large)
Padding: 8px 16px
```
#### Состояния кнопок
```
Default: opacity 1
Hover: brightness 0.95, shadow elevation 2
Active: brightness 0.9
Disabled: opacity 0.38, cursor not-allowed
Loading: показать CircularProgress (20px), disabled
```
#### Инпуты
```
Height: 40px
Border: 1px solid #E0E0E0
Border Radius: 4px
Padding: 8px 12px
Focus: border-color: Primary, box-shadow: 0 0 0 2px Primary/20%
Error: border-color: #D32F2F, helper text red
Disabled: background: #F5F5F5, opacity 0.6
```
#### Таблица
```
Header:
Background: #FAFAFA
Font: 14px, weight 500
Height: 48px
Border: 1px solid #E0E0E0 (bottom)
Row:
Height: 52px
Border: 1px solid #E0E0E0 (bottom)
Hover: background #F5F5F5
Row (colored):
Background: соответствующий цвет из палитры маркировки
Hover: darken 5%
Cell:
Padding: 16px
Vertical align: middle
```
#### Dropdown/Select
```
Trigger: как Input
Menu:
Background: #FFFFFF
Shadow: 0 2px 8px rgba(0,0,0,0.15)
Border Radius: 4px
Max Height: 300px (scroll)
Item:
Height: 40px
Padding: 8px 16px
Hover: background #F5F5F5
Selected: background Primary/10%, color Primary
```
#### Модальные окна
```
Overlay: rgba(0, 0, 0, 0.5)
Background: #FFFFFF
Border Radius: 8px
Shadow: 0 8px 32px rgba(0,0,0,0.2)
Width: 480px (small), 640px (medium), 800px (large)
Max Height: 90vh
Padding: 24px
Header:
Font: H2
Border: 1px solid #E0E0E0 (bottom)
Padding: 24px 24px 16px
Footer:
Border: 1px solid #E0E0E0 (top)
Padding: 16px 24px
Justify: flex-end
Gap: 12px
```
### 5.4 Состояния загрузки
#### Skeleton Loader
```
Для таблицы:
- Показать 5 строк skeleton
- Анимация: shimmer effect (gradient slide)
- Background: #E0E0E0
- Highlight: #F5F5F5
Для карточек/полей:
- Прямоугольники с закруглением 4px
- Высота соответствует контенту
```
#### Spinner (CircularProgress)
```
Size: 24px (в кнопках), 40px (в контенте)
Color: Primary
Thickness: 3.6
```
#### Inline Loading (в ячейках таблицы)
```
Показать маленький spinner (16px) справа от текста
Текст затемнить (opacity 0.5)
```
### 5.5 Анимации
```
Duration:
Fast: 150ms (hover effects)
Normal: 250ms (transitions)
Slow: 350ms (modals, large elements)
Easing:
Standard: cubic-bezier(0.4, 0, 0.2, 1)
Enter: cubic-bezier(0.0, 0, 0.2, 1)
Exit: cubic-bezier(0.4, 0, 1, 1)
Drag & Drop:
Item lift: scale 1.02, shadow elevation 8
Drop zone: border 2px dashed Primary, background Primary/5%
```
### 5.6 Иконки
Использовать **Material Icons** (MUI Icons).
```
Размеры:
Small: 18px
Medium: 24px (default)
Large: 36px
Основные иконки:
Добавить: Add (+)
Редактировать: Edit (карандаш)
Удалить: Delete (корзина)
Drag: DragIndicator (⋮⋮)
Фильтр: FilterList
Поиск: Search (🔍)
Комментарий: ChatBubbleOutline
AI: AutoAwesome (✨) или SmartToy (🤖)
Развернуть: ExpandMore
Свернуть: ExpandLess
```
### 5.7 Отступы и сетка
```
Spacing unit: 8px
Margins/Paddings:
xs: 4px
sm: 8px
md: 16px
lg: 24px
xl: 32px
Container:
Max width: 1440px
Padding: 24px (desktop), 16px (tablet), 12px (mobile)
Table:
Min width: 1024px
Horizontal scroll на меньших экранах
```
### 5.8 Breakpoints
```
xs: 0px
sm: 600px
md: 900px
lg: 1200px
xl: 1536px
Desktop-first approach:
Основной дизайн для lg+ (1200px+)
Адаптация для md (900-1199px)
Горизонтальный скролл для sm и ниже
```
---
## 6. Error States
### 6.1 Validation Errors
```
Inline под полем:
Color: #D32F2F
Font: 12px
Icon: ErrorOutline (слева от текста)
Margin top: 4px
```
### 6.2 API Errors
```
Toast notification (Snackbar):
Position: bottom-left
Duration: 5000ms (auto-hide)
Background: #D32F2F
Color: #FFFFFF
Action: "Повторить" (если применимо)
```
### 6.3 Empty States
```
Центрированный блок:
Icon: 64px, Grey 400
Title: H2, Grey 700
Description: Body1, Grey 500
Action: Primary Button
Примеры:
Нет идей: "Список пуст. Создайте первую идею!"
Нет команды: "Добавьте членов команды для AI-оценки"
Нет результатов: "По вашему запросу ничего не найдено"
```
---
## 7. Авторизация (Keycloak)
### 7.1 Конфигурация Keycloak
| Параметр | Значение |
|----------|----------|
| URL | https://auth.vigdorov.ru |
| Realm | `team-planner` |
| Client ID | `team-planner-frontend` |
| Client Type | Public (no secret) |
| Authentication Flow | Authorization Code + PKCE |
### 7.2 Настройка Client в Keycloak
```
Client authentication: OFF (public client)
Standard flow: ON
Direct access grants: OFF
Valid redirect URIs: http://localhost:4000/*
Web origins: http://localhost:4000
```
### 7.3 Environment Variables
**Backend (.env):**
```
KEYCLOAK_REALM_URL=https://auth.vigdorov.ru/realms/team-planner
```
**Frontend (.env):**
```
VITE_KEYCLOAK_URL=https://auth.vigdorov.ru
VITE_KEYCLOAK_REALM=team-planner
VITE_KEYCLOAK_CLIENT_ID=team-planner-frontend
```
### 7.4 JWT Validation (Backend)
```typescript
// Валидация через JWKS (публичные ключи)
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${realmUrl}/protocol/openid-connect/certs`,
})
// Проверки
issuer: KEYCLOAK_REALM_URL
algorithms: ['RS256']
```
### 7.5 Защищённые и публичные endpoints
| Endpoint | Доступ | Декоратор |
|----------|--------|-----------|
| `GET /` | Public | `@Public()` |
| `GET /health` | Public | `@Public()` |
| `GET /api/ideas` | Protected | — |
| `POST /api/ideas` | Protected | — |
| `PATCH /api/ideas/:id` | Protected | — |
| `DELETE /api/ideas/:id` | Protected | — |
| Все остальные | Protected | — |