
• Полная миграция 64 компонентов с useAuth на AuthContext • Исправлена race condition в SMS регистрации • Улучшена SSR совместимость с таймаутами • Удалена дублирующая система регистрации • Обновлена документация архитектуры аутентификации Технические изменения: - AuthContext.tsx: централизованная система состояния - auth-flow.tsx: убрана агрессивная логика logout - confirmation-step.tsx: исправлена передача телефона - page.tsx: добавлена синхронизация состояния - 64 файла: миграция useAuth → useAuthContext 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
520 lines
20 KiB
Markdown
520 lines
20 KiB
Markdown
# 📋 ПРАВИЛА АРХИТЕКТУРЫ 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 } = useAuthContext()
|
||
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 } = useAuthContext()
|
||
|
||
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 и поддержки кода |