Files
sfera-new/docs/presentation-layer/SIDEBAR_ARCHITECTURE_RULES.md
2025-08-30 15:51:41 +03:00

520 lines
20 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.

# 📋 ПРАВИЛА АРХИТЕКТУРЫ SIDEBAR КОМПОНЕНТОВ SFERA
> **Статус**: ✅ Архитектурный стандарт SFERA
> **Дата создания**: 28.08.2025
> **Связанные документы**:
> - [URL_ROUTING_RULES.md](./URL_ROUTING_RULES.md)
> - [COMPONENT_ARCHITECTURE.md](./COMPONENT_ARCHITECTURE.md)
> - [DOMAIN_MODEL.md](../core/DOMAIN_MODEL.md)
---
## 🎯 ПРОБЛЕМА И РЕШЕНИЕ
### ❌ ТЕКУЩАЯ ПРОБЛЕМА
- **Монолитный sidebar.tsx** - 740 строк кода
- **Смешанная логика** всех ролей в одном файле
- **Условная навигация** с `user?.organization?.type === 'ROLE'`
- **Сложность поддержки** и добавления новых пунктов меню
- **Отсутствие изоляции** между ролями
### ✅ АРХИТЕКТУРНОЕ РЕШЕНИЕ
- **4 независимых sidebar** компонента для каждой роли
- **Изолированная навигация** без условий
- **Базовый компонент** с общей логикой
- **Ролевой роутер** для автоматического выбора sidebar
---
## 🏗️ АРХИТЕКТУРНЫЕ ПРИНЦИПЫ
### ПРИНЦИП 1: ИЗОЛЯЦИЯ ПО РОЛЯМ
```typescript
// ❌ СТАРЫЙ ПОДХОД - условия в одном файле
{user?.organization?.type === 'SELLER' && <SellerNavigation />}
{user?.organization?.type === 'FULFILLMENT' && <FulfillmentNavigation />}
// ✅ НОВЫЙ ПОДХОД - отдельные компоненты
<SellerSidebar /> // Только навигация селлера
<FulfillmentSidebar /> // Только навигация фулфилмента
<LogistSidebar /> // Только навигация логистики
<WholesaleSidebar /> // Только навигация поставщика
```
### ПРИНЦИП 2: ЕДИНАЯ БАЗОВАЯ АРХИТЕКТУРА
Все sidebar наследуют от базового компонента:
```typescript
export function BaseSidebar({
navigationItems,
user,
notifications
}: BaseSidebarProps) {
return (
<div className="sidebar-base">
<UserProfile user={user} /> // Общий для всех
<CollapseButton /> // Общий для всех
<Navigation items={navigationItems} /> // Уникальный для роли
<Notifications config={notifications} /> // Уникальный для роли
<LogoutButton /> // Общий для всех
</div>
)
}
```
### ПРИНЦИП 3: СООТВЕТСТВИЕ URL ROUTING RULES
Каждый пункт навигации использует новые ролевые URL:
```typescript
// ✅ Правильные URL согласно URL_ROUTING_RULES
const SELLER_NAVIGATION = [
{ path: '/seller/home' }, // не /home
{ path: '/seller/supplies/goods/cards' }, // не /supplies
{ path: '/seller/warehouse' }, // не /wb-warehouse
{ path: '/seller/statistics' } // не /seller-statistics
]
```
---
## 📁 ФАЙЛОВАЯ СТРУКТУРА
```
src/components/dashboard/sidebar/
├── index.tsx # 🔄 Роутер sidebar (выбор по роли)
├── BaseSidebar.tsx # 🔧 Базовый компонент
├── SellerSidebar.tsx # 🛒 Навигация селлера
├── FulfillmentSidebar.tsx # 🏭 Навигация фулфилмента
├── WholesaleSidebar.tsx # 🏪 Навигация поставщика
├── LogistSidebar.tsx # 🚛 Навигация логистики
├── types.ts # 🔷 TypeScript интерфейсы
└── components/ # 📦 Общие компоненты
├── UserProfile.tsx
├── CollapseButton.tsx
├── Navigation.tsx
├── Notifications.tsx
└── LogoutButton.tsx
```
---
## 🔧 ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ
### 1. БАЗОВЫЕ ТИПЫ
```typescript
// types.ts
export interface NavigationItem {
id: string
label: string
icon: React.ComponentType<{ className?: string }>
path: string
badge?: number
notification?: React.ComponentType
isActive?: boolean
}
export interface SidebarUser {
id: string
organization: {
type: 'SELLER' | 'FULFILLMENT' | 'WHOLESALE' | 'LOGIST'
name: string
}
avatar?: string
managerName?: string
}
export interface NotificationConfig {
supplies?: number
orders?: number
messages?: number
requests?: number
}
export interface BaseSidebarProps {
navigationItems: NavigationItem[]
user: SidebarUser
notifications: NotificationConfig
isCollapsed: boolean
onToggle: () => void
}
```
### 2. БАЗОВЫЙ КОМПОНЕНТ
```typescript
// BaseSidebar.tsx
import { UserProfile } from './components/UserProfile'
import { CollapseButton } from './components/CollapseButton'
import { Navigation } from './components/Navigation'
import { Notifications } from './components/Notifications'
import { LogoutButton } from './components/LogoutButton'
export function BaseSidebar({
navigationItems,
user,
notifications,
isCollapsed,
onToggle
}: BaseSidebarProps) {
return (
<div className={`sidebar-base ${isCollapsed ? 'w-16' : 'w-56'}
fixed left-4 top-4 bottom-4 bg-white/10 backdrop-blur-xl
border border-white/20 rounded-2xl transition-all duration-300`}>
<CollapseButton onClick={onToggle} isCollapsed={isCollapsed} />
<div className="flex flex-col h-full justify-between p-3">
<div>
<UserProfile user={user} isCollapsed={isCollapsed} />
<Navigation items={navigationItems} isCollapsed={isCollapsed} />
</div>
<LogoutButton isCollapsed={isCollapsed} />
</div>
</div>
)
}
```
### 3. РОЛЕВОЙ SIDEBAR (ПРИМЕР)
```typescript
// LogistSidebar.tsx
import { Home, Truck, Map, MessageCircle, DollarSign,
Handshake, Store, TrendingUp, Settings } from 'lucide-react'
import { useAuth } from '@/hooks/useAuth'
import { usePathname } from 'next/navigation'
import { useSidebar } from '@/hooks/useSidebar'
import { BaseSidebar } from './BaseSidebar'
import { NavigationItem } from './types'
export function LogistSidebar() {
const { user } = useAuth()
const pathname = usePathname()
const { isCollapsed, toggleSidebar } = useSidebar()
const navigationItems: NavigationItem[] = [
{
id: 'home',
label: 'Главная',
icon: Home,
path: '/logistics/home',
isActive: pathname === '/logistics/home'
},
{
id: 'orders',
label: 'Перевозки',
icon: Truck,
path: '/logistics/orders/pending',
isActive: pathname.startsWith('/logistics/orders')
},
{
id: 'routes',
label: 'Маршруты',
icon: Map,
path: '/logistics/routes',
isActive: pathname.startsWith('/logistics/routes')
},
{
id: 'messenger',
label: 'Мессенджер',
icon: MessageCircle,
path: '/logistics/messenger',
isActive: pathname.startsWith('/logistics/messenger')
},
{
id: 'economics',
label: 'Экономика',
icon: DollarSign,
path: '/logistics/economics',
isActive: pathname.startsWith('/logistics/economics')
},
{
id: 'partners',
label: 'Партнёры',
icon: Handshake,
path: '/logistics/partners',
isActive: pathname.startsWith('/logistics/partners')
},
{
id: 'market',
label: 'Маркет',
icon: Store,
path: '/logistics/market',
isActive: pathname.startsWith('/logistics/market')
},
{
id: 'exchange',
label: 'Биржа',
icon: TrendingUp,
path: '/logistics/exchange',
isActive: pathname.startsWith('/logistics/exchange')
},
{
id: 'settings',
label: 'Настройки',
icon: Settings,
path: '/logistics/settings',
isActive: pathname.startsWith('/logistics/settings')
}
]
return (
<BaseSidebar
navigationItems={navigationItems}
user={user}
notifications={{
orders: 0, // логистические заказы
messages: 0 // непрочитанные сообщения
}}
isCollapsed={isCollapsed}
onToggle={toggleSidebar}
/>
)
}
```
### 4. ГЛАВНЫЙ РОУТЕР
```typescript
// index.tsx
import { useAuth } from '@/hooks/useAuth'
import { SellerSidebar } from './SellerSidebar'
import { FulfillmentSidebar } from './FulfillmentSidebar'
import { WholesaleSidebar } from './WholesaleSidebar'
import { LogistSidebar } from './LogistSidebar'
export function Sidebar() {
const { user } = useAuth()
if (!user?.organization?.type) {
return (
<div className="w-56 h-screen bg-white/10 backdrop-blur-xl
border border-white/20 rounded-2xl p-4">
<div className="animate-pulse">Загрузка...</div>
</div>
)
}
// Роутинг на основе типа организации
switch (user.organization.type) {
case 'SELLER':
return <SellerSidebar />
case 'FULFILLMENT':
return <FulfillmentSidebar />
case 'WHOLESALE':
return <WholesaleSidebar />
case 'LOGIST':
return <LogistSidebar />
default:
return (
<div className="w-56 h-screen bg-red-500/10 backdrop-blur-xl
border border-red-500/20 rounded-2xl p-4">
<div className="text-red-300">
Неизвестный тип организации: {user.organization.type}
</div>
</div>
)
}
}
```
---
## 📋 НАВИГАЦИОННЫЕ СПЕЦИФИКАЦИИ ПО РОЛЯМ
### 🛒 SELLER SIDEBAR
```typescript
const SELLER_NAVIGATION = [
{ id: 'home', label: 'Главная', icon: Home, path: '/seller/home' },
{ id: 'supplies', label: 'Мои поставки', icon: Truck, path: '/seller/supplies/goods/cards' },
{ id: 'warehouse', label: 'Склад WB', icon: Warehouse, path: '/seller/warehouse' },
{ id: 'statistics', label: 'Статистика', icon: BarChart3, path: '/seller/statistics' },
{ id: 'messenger', label: 'Мессенджер', icon: MessageCircle, path: '/seller/messenger' },
{ id: 'economics', label: 'Экономика', icon: DollarSign, path: '/seller/economics' },
{ id: 'partners', label: 'Партнёры', icon: Handshake, path: '/seller/partners' },
{ id: 'market', label: 'Маркет', icon: Store, path: '/seller/market' },
{ id: 'exchange', label: 'Биржа', icon: TrendingUp, path: '/seller/exchange' },
{ id: 'settings', label: 'Настройки', icon: Settings, path: '/seller/settings' }
]
```
### 🏭 FULFILLMENT SIDEBAR
```typescript
const FULFILLMENT_NAVIGATION = [
{ id: 'home', label: 'Главная', icon: Home, path: '/fulfillment/home' },
{ id: 'supplies', label: 'Входящие поставки', icon: Truck, path: '/fulfillment/supplies/goods/receiving' },
{ id: 'warehouse', label: 'Склад', icon: Warehouse, path: '/fulfillment/warehouse' },
{ id: 'services', label: 'Услуги', icon: Wrench, path: '/fulfillment/services' },
{ id: 'employees', label: 'Сотрудники', icon: Users, path: '/fulfillment/employees' },
{ id: 'statistics', label: 'Статистика', icon: BarChart3, path: '/fulfillment/statistics' },
{ id: 'messenger', label: 'Мессенджер', icon: MessageCircle, path: '/fulfillment/messenger' },
{ id: 'economics', label: 'Экономика', icon: DollarSign, path: '/fulfillment/economics' },
{ id: 'partners', label: 'Партнёры', icon: Handshake, path: '/fulfillment/partners' },
{ id: 'market', label: 'Маркет', icon: Store, path: '/fulfillment/market' },
{ id: 'exchange', label: 'Биржа', icon: TrendingUp, path: '/fulfillment/exchange' },
{ id: 'settings', label: 'Настройки', icon: Settings, path: '/fulfillment/settings' }
]
```
### 🏪 WHOLESALE SIDEBAR
```typescript
const WHOLESALE_NAVIGATION = [
{ id: 'home', label: 'Главная', icon: Home, path: '/wholesale/home' },
{ id: 'orders', label: 'Входящие заказы', icon: Truck, path: '/wholesale/orders' },
{ id: 'catalog', label: 'Каталог товаров', icon: Store, path: '/wholesale/catalog/goods' },
{ id: 'warehouse', label: 'Склад', icon: Warehouse, path: '/wholesale/warehouse' },
{ id: 'messenger', label: 'Мессенджер', icon: MessageCircle, path: '/wholesale/messenger' },
{ id: 'economics', label: 'Экономика', icon: DollarSign, path: '/wholesale/economics' },
{ id: 'partners', label: 'Партнёры', icon: Handshake, path: '/wholesale/partners' },
{ id: 'market', label: 'Маркет', icon: Store, path: '/wholesale/market' },
{ id: 'exchange', label: 'Биржа', icon: TrendingUp, path: '/wholesale/exchange' },
{ id: 'settings', label: 'Настройки', icon: Settings, path: '/wholesale/settings' }
]
```
### 🚛 LOGIST SIDEBAR
```typescript
const LOGIST_NAVIGATION = [
{ id: 'home', label: 'Главная', icon: Home, path: '/logistics/home' },
{ id: 'orders', label: 'Перевозки', icon: Truck, path: '/logistics/orders/pending' },
{ id: 'routes', label: 'Маршруты', icon: Map, path: '/logistics/routes' },
{ id: 'messenger', label: 'Мессенджер', icon: MessageCircle, path: '/logistics/messenger' },
{ id: 'economics', label: 'Экономика', icon: DollarSign, path: '/logistics/economics' },
{ id: 'partners', label: 'Партнёры', icon: Handshake, path: '/logistics/partners' },
{ id: 'market', label: 'Маркет', icon: Store, path: '/logistics/market' },
{ id: 'exchange', label: 'Биржа', icon: TrendingUp, path: '/logistics/exchange' },
{ id: 'settings', label: 'Настройки', icon: Settings, path: '/logistics/settings' }
]
```
---
## 🎯 ПРЕИМУЩЕСТВА НОВОЙ АРХИТЕКТУРЫ
### 📊 ТЕХНИЧЕСКИЕ ПРЕИМУЩЕСТВА
**Читаемость** - каждая роль в отдельном файле (~100 строк vs 740)
**Поддержка** - легко изменить навигацию конкретной роли
**Тестирование** - изолированное тестирование каждой роли
**Производительность** - загружается только нужная навигация
**Безопасность** - физическая невозможность показать чужие пункты
### 🏗️ АРХИТЕКТУРНЫЕ ПРЕИМУЩЕСТВА
**Масштабируемость** - легко добавлять новые роли
**Изоляция** - изменения в одной роли не влияют на другие
**Переиспользование** - общая логика в BaseSidebar
**Типизация** - строгие типы для каждой роли
**Консистентность** - единый интерфейс для всех sidebar
### 💼 БИЗНЕС ПРЕИМУЩЕСТВА
**UX** - каждая роль видит только релевантную навигацию
**Скорость разработки** - параллельная работа над разными ролями
**Качество** - меньше ошибок из-за изоляции кода
**Гибкость** - быстрая адаптация навигации под потребности роли
---
## 📈 ПЛАН МИГРАЦИИ
### ФАЗА 1: ПОДГОТОВКА (1-2 дня)
- [ ] Создать папку `src/components/dashboard/sidebar/`
- [ ] Реализовать `types.ts` с базовыми интерфейсами
- [ ] Создать `BaseSidebar.tsx` с общей логикой
- [ ] Создать базовые компоненты (`UserProfile`, `Navigation`, etc.)
### ФАЗА 2: ПЕРВЫЙ РОЛЕВОЙ SIDEBAR (2-3 дня)
- [ ] Реализовать `LogistSidebar.tsx` как пилотный проект
- [ ] Протестировать работу с существующими хуками
- [ ] Убедиться в корректной работе уведомлений
- [ ] Проверить соответствие URL_ROUTING_RULES
### ФАЗА 3: ОСТАЛЬНЫЕ РОЛЕВЫЕ SIDEBAR (3-4 дня)
- [ ] Создать `SellerSidebar.tsx`
- [ ] Создать `FulfillmentSidebar.tsx`
- [ ] Создать `WholesaleSidebar.tsx`
- [ ] Протестировать все роли
### ФАЗА 4: ИНТЕГРАЦИЯ И CLEANUP (1-2 дня)
- [ ] Создать главный роутер `index.tsx`
- [ ] Обновить импорты в layout компонентах
- [ ] Удалить старый `sidebar.tsx`
- [ ] Провести финальное тестирование
### ФАЗА 5: ТЕСТИРОВАНИЕ (2-3 дня)
- [ ] Unit тесты для каждого ролевого sidebar
- [ ] Интеграционные тесты навигации
- [ ] E2E тесты пользовательских сценариев
- [ ] Проверка производительности
---
## ⚠️ ВАЖНЫЕ ЗАМЕЧАНИЯ
### ОБРАТНАЯ СОВМЕСТИМОСТЬ
- Старые хуки (`useSidebar`, `useAuth`) должны работать без изменений
- GraphQL запросы для уведомлений остаются теми же
- Стили и анимации сохраняются
### БЕЗОПАСНОСТЬ
- Каждый sidebar имеет доступ только к своим данным
- Невозможно случайно показать навигацию другой роли
- Строгая типизация предотвращает ошибки
### ПРОИЗВОДИТЕЛЬНОСТЬ
- Bundle splitting по ролям
- Lazy loading неиспользуемых sidebar
- Мемоизация навигационных элементов
---
## 🧪 ТЕСТИРОВАНИЕ
### Unit тесты
```typescript
// LogistSidebar.test.tsx
describe('LogistSidebar', () => {
it('should render correct navigation items', () => {
render(<LogistSidebar />)
expect(screen.getByText('Перевозки')).toBeInTheDocument()
expect(screen.getByText('Маршруты')).toBeInTheDocument()
expect(screen.queryByText('Услуги')).not.toBeInTheDocument() // только для фулфилмента
})
it('should highlight active navigation item', () => {
mockPathname('/logistics/orders/pending')
render(<LogistSidebar />)
expect(screen.getByText('Перевозки')).toHaveClass('active')
})
})
```
### Интеграционные тесты
```typescript
// Sidebar.test.tsx
describe('Sidebar Routing', () => {
it('should render LogistSidebar for LOGIST users', () => {
mockUser({ organization: { type: 'LOGIST' } })
render(<Sidebar />)
expect(screen.getByText('Перевозки')).toBeInTheDocument()
expect(screen.queryByText('Входящие поставки')).not.toBeInTheDocument()
})
})
```
---
## 📝 ИСТОРИЯ ИЗМЕНЕНИЙ
| Дата | Версия | Описание | Автор |
|------------|--------|---------------------------------------|----------|
| 28.08.2025 | 1.0 | Первая версия правил sidebar | AI |
| 28.08.2025 | 1.1 | Добавлены спецификации по ролям | AI |
| 28.08.2025 | 1.2 | Добавлен план миграции | AI |
---
**Создано**: В рамках унификации URL системы SFERA
**Связано**: Модульная архитектура компонентов и ролевая маршрутизация
**Цель**: Изоляция навигации по ролям для улучшения UX и поддержки кода