Files
team-planner/ARCHITECTURE.md
2026-01-14 01:10:01 +03:00

1099 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Архитектура Team Planner
---
## 1. C4 Model
### 1.1 Level 1: System Context
```mermaid
flowchart TB
subgraph external [" "]
User["👤 Пользователь<br/><i>Член команды разработки</i>"]
AI["🤖 AI Proxy Service<br/><i>LLM для оценки задач</i>"]
KC["🔐 Keycloak<br/><i>auth.vigdorov.ru<br/>Identity Provider</i>"]
end
subgraph system ["Team Planner"]
TP["🎯 Team Planner<br/><i>Приложение для управления<br/>бэклогом идей команды</i>"]
end
User -->|"Управляет идеями,<br/>командой, комментариями<br/>[HTTPS]"| TP
User -->|"Авторизация<br/>[OIDC/PKCE]"| KC
KC -->|"JWT токены"| TP
TP -->|"Запросы на оценку<br/>трудозатрат<br/>[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["👤 Пользователь<br/><i>Член команды разработки</i>"]
KC["🔐 Keycloak<br/><i>auth.vigdorov.ru</i>"]
subgraph TeamPlanner ["Team Planner"]
SPA["📱 Frontend SPA<br/><i>React, TypeScript, MUI</i><br/><br/>Веб-интерфейс для<br/>работы с идеями"]
API["⚙️ Backend API<br/><i>NestJS, TypeScript</i><br/><br/>REST API + WebSocket"]
DB[("🗄️ Database<br/><i>PostgreSQL</i><br/><br/>Хранение идей,<br/>команды, комментариев")]
end
AI["🤖 AI Proxy Service<br/><i>LLM для оценки задач</i>"]
User -->|"Использует<br/>[HTTPS]"| SPA
User <-->|"OIDC Login<br/>[Redirect]"| KC
SPA -->|"API запросы<br/>[REST + Bearer JWT]"| API
API -.->|"Валидация JWT<br/>[JWKS]"| KC
API -->|"Читает/пишет<br/>[TypeORM]"| DB
API -->|"Оценка трудозатрат<br/>[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<br/>(React)
participant KC as Keycloak<br/>(auth.vigdorov.ru)
participant BE as Backend<br/>(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<br/>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<br/>(React)
participant BE as Backend<br/>(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<br/>(React)
participant BE as Backend<br/>(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<br/>(React)
participant BE as Backend<br/>(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<br/>(React)
participant BE as Backend<br/>(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 с описанием идеи<br/>и составом команды
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<br/>(React)
participant BE as Backend<br/>(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<br/>(React)
participant BE as Backend<br/>(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<TeamMember>`
**Response 200:** `TeamMember`
#### DELETE /api/team/members/:id
Удаление члена команды.
**Response 204:** No Content
#### GET /api/team/summary
Сводка по команде.
**Response 200:**
```typescript
{
totalMembers: number;
byRole: Record<TeamRole, number>;
}
```
### 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 | — |