
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
20 KiB
📋 ПРАВИЛА АРХИТЕКТУРЫ SIDEBAR КОМПОНЕНТОВ SFERA
Статус: ✅ Архитектурный стандарт SFERA
Дата создания: 28.08.2025
Связанные документы:
🎯 ПРОБЛЕМА И РЕШЕНИЕ
❌ ТЕКУЩАЯ ПРОБЛЕМА
- Монолитный sidebar.tsx - 740 строк кода
- Смешанная логика всех ролей в одном файле
- Условная навигация с
user?.organization?.type === 'ROLE'
- Сложность поддержки и добавления новых пунктов меню
- Отсутствие изоляции между ролями
✅ АРХИТЕКТУРНОЕ РЕШЕНИЕ
- 4 независимых sidebar компонента для каждой роли
- Изолированная навигация без условий
- Базовый компонент с общей логикой
- Ролевой роутер для автоматического выбора sidebar
🏗️ АРХИТЕКТУРНЫЕ ПРИНЦИПЫ
ПРИНЦИП 1: ИЗОЛЯЦИЯ ПО РОЛЯМ
// ❌ СТАРЫЙ ПОДХОД - условия в одном файле
{user?.organization?.type === 'SELLER' && <SellerNavigation />}
{user?.organization?.type === 'FULFILLMENT' && <FulfillmentNavigation />}
// ✅ НОВЫЙ ПОДХОД - отдельные компоненты
<SellerSidebar /> // Только навигация селлера
<FulfillmentSidebar /> // Только навигация фулфилмента
<LogistSidebar /> // Только навигация логистики
<WholesaleSidebar /> // Только навигация поставщика
ПРИНЦИП 2: ЕДИНАЯ БАЗОВАЯ АРХИТЕКТУРА
Все sidebar наследуют от базового компонента:
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:
// ✅ Правильные 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. БАЗОВЫЕ ТИПЫ
// 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. БАЗОВЫЙ КОМПОНЕНТ
// 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 (ПРИМЕР)
// 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. ГЛАВНЫЙ РОУТЕР
// 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
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
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
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
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 тесты
// 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')
})
})
Интеграционные тесты
// 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 и поддержки кода