# Архитектура 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 | — |