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

20 KiB
Raw Blame History

📋 ПРАВИЛА АРХИТЕКТУРЫ 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 и поддержки кода