Files
sfera-new/src/components/dashboard/sidebar.tsx

455 lines
21 KiB
TypeScript
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.

"use client"
import { useAuth } from '@/hooks/useAuth'
import { useSidebar } from '@/hooks/useSidebar'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
import { useRouter, usePathname } from 'next/navigation'
import { useQuery } from '@apollo/client'
import { GET_CONVERSATIONS, GET_INCOMING_REQUESTS } from '@/graphql/queries'
import {
Settings,
LogOut,
Store,
MessageCircle,
Wrench,
Warehouse,
Users,
Truck,
Handshake,
ChevronLeft,
ChevronRight
} from 'lucide-react'
export function Sidebar() {
const { user, logout } = useAuth()
const router = useRouter()
const pathname = usePathname()
const { isCollapsed, toggleSidebar } = useSidebar()
// Загружаем список чатов для подсчета непрочитанных сообщений
const { data: conversationsData } = useQuery(GET_CONVERSATIONS, {
pollInterval: 60000, // Обновляем каждую минуту в сайдбаре - этого достаточно
fetchPolicy: 'cache-first',
errorPolicy: 'ignore', // Игнорируем ошибки чтобы не ломать сайдбар
notifyOnNetworkStatusChange: false, // Плавные обновления без мерцания
})
// Загружаем входящие заявки для подсчета новых запросов
const { data: incomingRequestsData } = useQuery(GET_INCOMING_REQUESTS, {
pollInterval: 60000, // Обновляем каждую минуту
fetchPolicy: 'cache-first',
errorPolicy: 'ignore',
notifyOnNetworkStatusChange: false,
})
const conversations = conversationsData?.conversations || []
const incomingRequests = incomingRequestsData?.incomingRequests || []
const totalUnreadCount = conversations.reduce((sum: number, conv: { unreadCount?: number }) => sum + (conv.unreadCount || 0), 0)
const incomingRequestsCount = incomingRequests.length
const getInitials = () => {
const orgName = getOrganizationName()
return orgName.charAt(0).toUpperCase()
}
const getOrganizationName = () => {
if (user?.organization?.name) {
return user.organization.name
}
if (user?.organization?.fullName) {
return user.organization.fullName
}
return 'Организация'
}
const getCabinetType = () => {
if (!user?.organization?.type) return 'Кабинет'
switch (user.organization.type) {
case 'FULFILLMENT':
return 'Фулфилмент'
case 'SELLER':
return 'Селлер'
case 'LOGIST':
return 'Логистика'
case 'WHOLESALE':
return 'Оптовик'
default:
return 'Кабинет'
}
}
const handleSettingsClick = () => {
router.push('/settings')
}
const handleMarketClick = () => {
router.push('/market')
}
const handleMessengerClick = () => {
router.push('/messenger')
}
const handleServicesClick = () => {
router.push('/services')
}
const handleWarehouseClick = () => {
router.push('/warehouse')
}
const handleEmployeesClick = () => {
router.push('/employees')
}
const handleSuppliesClick = () => {
// Для каждого типа кабинета свой роут
switch (user?.organization?.type) {
case 'FULFILLMENT':
router.push('/fulfillment-supplies')
break
case 'SELLER':
router.push('/supplies')
break
case 'WHOLESALE':
router.push('/supplies')
break
case 'LOGIST':
router.push('/logistics')
break
default:
router.push('/supplies')
}
}
const handlePartnersClick = () => {
router.push('/partners')
}
const isSettingsActive = pathname === '/settings'
const isMarketActive = pathname.startsWith('/market')
const isMessengerActive = pathname.startsWith('/messenger')
const isServicesActive = pathname.startsWith('/services')
const isWarehouseActive = pathname.startsWith('/warehouse')
const isEmployeesActive = pathname.startsWith('/employees')
const isSuppliesActive = pathname.startsWith('/supplies') || pathname.startsWith('/fulfillment-supplies') || pathname.startsWith('/logistics')
const isPartnersActive = pathname.startsWith('/partners')
return (
<div className="relative">
{/* Основной сайдбар */}
<div className={`fixed left-4 top-4 bottom-4 ${isCollapsed ? 'w-16' : 'w-56'} bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl ${isCollapsed ? 'p-2' : 'p-3'} transition-all duration-300 ease-in-out z-50`}>
{/* ОХУЕННАЯ кнопка сворачивания - на правом краю сайдбара */}
<div className="absolute -right-6 top-1/2 -translate-y-1/2 z-[999]">
<div className="relative group">
{/* Основная кнопка с безопасными эффектами */}
<Button
variant="ghost"
size="icon"
onClick={toggleSidebar}
className="relative h-12 w-12 rounded-full bg-gradient-to-br from-white/20 to-white/5 border border-white/30 hover:from-white/30 hover:to-white/10 transition-all duration-300 ease-out hover:scale-110 active:scale-95 backdrop-blur-xl shadow-lg hover:shadow-xl hover:shadow-purple-500/20 group-hover:border-purple-300/50"
title={isCollapsed ? "Развернуть сайдбар" : "Свернуть сайдбар"}
>
{/* Простая анимированная иконка */}
<div className="transition-transform duration-300 ease-out group-hover:scale-110">
{isCollapsed ? (
<ChevronRight className="h-6 w-6 text-white/80 group-hover:text-white transition-colors duration-300" />
) : (
<ChevronLeft className="h-6 w-6 text-white/80 group-hover:text-white transition-colors duration-300" />
)}
</div>
{/* Простое свечение при наведении */}
<div className="absolute inset-0 rounded-full bg-gradient-to-r from-purple-500/0 to-blue-500/0 group-hover:from-purple-500/10 group-hover:to-blue-500/10 transition-all duration-500"></div>
</Button>
{/* Подсказка только в свернутом состоянии */}
{isCollapsed && (
<div className="absolute left-full ml-3 top-1/2 -translate-y-1/2 whitespace-nowrap opacity-0 group-hover:opacity-100 transition-all duration-300">
<div className="bg-gradient-to-r from-purple-500/20 to-blue-500/20 backdrop-blur-xl border border-white/20 rounded-lg px-3 py-2">
<div className="text-sm text-white font-medium flex items-center space-x-2">
<div className="w-2 h-2 bg-green-400 rounded-full animate-pulse"></div>
<span> Развернуть</span>
</div>
</div>
</div>
)}
</div>
</div>
<div className="flex flex-col h-full">
{/* Информация о пользователе */}
<div className="bg-white/5 backdrop-blur border border-white/30 rounded-xl mb-3 p-2.5 shadow-sm">
{!isCollapsed ? (
// Развернутое состояние - без карточки
<div className="flex items-center space-x-2.5">
<div className="relative flex-shrink-0">
<Avatar className="h-8 w-8 ring-2 ring-white/40">
{user?.avatar ? (
<AvatarImage
src={user.avatar}
alt="Аватар пользователя"
className="w-full h-full object-cover"
/>
) : null}
<AvatarFallback className="bg-gradient-to-br from-purple-500 to-purple-600 text-white text-xs font-semibold">
{getInitials()}
</AvatarFallback>
</Avatar>
<div className="absolute -bottom-0.5 -right-0.5 w-2 h-2 bg-green-400 rounded-full border-2 border-white/40"></div>
</div>
<div className="flex-1 min-w-0">
<p className="text-white text-xs font-medium mb-0.5 break-words" title={getOrganizationName()}>
{getOrganizationName()}
</p>
<div className="flex items-center space-x-1">
<div className="w-1 h-1 bg-purple-400 rounded-full flex-shrink-0"></div>
<p className="text-white/50 text-[10px]">
{getCabinetType()}
</p>
</div>
</div>
</div>
) : (
// Свернутое состояние - только аватар
<div className="flex justify-center">
<div className="relative" title={`${getOrganizationName()} - ${getCabinetType()}`}>
<Avatar className="h-7 w-7 ring-2 ring-white/40">
{user?.avatar ? (
<AvatarImage
src={user.avatar}
alt="Аватар пользователя"
className="w-full h-full object-cover"
/>
) : null}
<AvatarFallback className="bg-gradient-to-br from-purple-500 to-purple-600 text-white text-[10px] font-semibold">
{getInitials()}
</AvatarFallback>
</Avatar>
<div className="absolute -bottom-0.5 -right-0.5 w-1.5 h-1.5 bg-green-400 rounded-full border-2 border-white/40"></div>
</div>
</div>
)}
</div>
{/* Навигация */}
<div className="space-y-1 mb-3 flex-1">
<Button
variant={isMarketActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
isMarketActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleMarketClick}
title={isCollapsed ? "Маркет" : ""}
>
<Store className={`${isCollapsed ? 'h-4 w-4' : 'h-4 w-4'} flex-shrink-0`} />
{!isCollapsed && <span className="ml-3">Маркет</span>}
</Button>
<Button
variant={isMessengerActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs relative ${
isMessengerActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleMessengerClick}
title={isCollapsed ? "Мессенджер" : ""}
>
<MessageCircle className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Мессенджер</span>}
{/* Индикатор непрочитанных сообщений */}
{totalUnreadCount > 0 && (
<div className={`absolute ${
isCollapsed
? 'top-1 right-1 w-3 h-3'
: 'top-2 right-2 w-4 h-4'
} bg-red-500 text-white text-xs rounded-full flex items-center justify-center font-bold`}>
{isCollapsed ? '' : (totalUnreadCount > 99 ? '99+' : totalUnreadCount)}
</div>
)}
</Button>
<Button
variant={isPartnersActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs relative ${
isPartnersActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handlePartnersClick}
title={isCollapsed ? "Партнёры" : ""}
>
<Handshake className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Партнёры</span>}
{/* Индикатор входящих заявок */}
{incomingRequestsCount > 0 && (
<div className={`absolute ${
isCollapsed
? 'top-1 right-1 w-3 h-3'
: 'top-2 right-2 w-4 h-4'
} bg-red-500 text-white text-xs rounded-full flex items-center justify-center font-bold`}>
{isCollapsed ? '' : (incomingRequestsCount > 99 ? '99+' : incomingRequestsCount)}
</div>
)}
</Button>
{/* Услуги - только для фулфилмент центров */}
{user?.organization?.type === 'FULFILLMENT' && (
<Button
variant={isServicesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
isServicesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleServicesClick}
title={isCollapsed ? "Услуги" : ""}
>
<Wrench className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Услуги</span>}
</Button>
)}
{/* Сотрудники - только для фулфилмент центров */}
{user?.organization?.type === 'FULFILLMENT' && (
<Button
variant={isEmployeesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
isEmployeesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleEmployeesClick}
title={isCollapsed ? "Сотрудники" : ""}
>
<Users className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Сотрудники</span>}
</Button>
)}
{/* Мои поставки - для селлеров */}
{user?.organization?.type === 'SELLER' && (
<Button
variant={isSuppliesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
isSuppliesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleSuppliesClick}
title={isCollapsed ? "Мои поставки" : ""}
>
<Truck className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Мои поставки</span>}
</Button>
)}
{/* Входящие поставки - для фулфилмент */}
{user?.organization?.type === 'FULFILLMENT' && (
<Button
variant={isSuppliesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
isSuppliesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleSuppliesClick}
title={isCollapsed ? "Входящие поставки" : ""}
>
<Truck className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Входящие поставки</span>}
</Button>
)}
{/* Отгрузки - для оптовиков */}
{user?.organization?.type === 'WHOLESALE' && (
<Button
variant={isSuppliesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
isSuppliesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleSuppliesClick}
title={isCollapsed ? "Отгрузки" : ""}
>
<Truck className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Отгрузки</span>}
</Button>
)}
{/* Перевозки - для логистов */}
{user?.organization?.type === 'LOGIST' && (
<Button
variant={isSuppliesActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
isSuppliesActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleSuppliesClick}
title={isCollapsed ? "Перевозки" : ""}
>
<Truck className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Перевозки</span>}
</Button>
)}
{/* Склад - только для оптовиков */}
{user?.organization?.type === 'WHOLESALE' && (
<Button
variant={isWarehouseActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
isWarehouseActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleWarehouseClick}
title={isCollapsed ? "Склад" : ""}
>
<Warehouse className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Склад</span>}
</Button>
)}
<Button
variant={isSettingsActive ? "secondary" : "ghost"}
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
isSettingsActive
? 'bg-white/20 text-white hover:bg-white/30'
: 'text-white/80 hover:bg-white/10 hover:text-white'
} cursor-pointer`}
onClick={handleSettingsClick}
title={isCollapsed ? "Настройки профиля" : ""}
>
<Settings className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Настройки профиля</span>}
</Button>
</div>
{/* Кнопка выхода */}
<div>
<Button
variant="ghost"
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-white/80 hover:bg-red-500/20 hover:text-red-300 cursor-pointer text-xs transition-all duration-200`}
onClick={logout}
title={isCollapsed ? "Выйти" : ""}
>
<LogOut className="h-4 w-4 flex-shrink-0" />
{!isCollapsed && <span className="ml-3">Выйти</span>}
</Button>
</div>
</div>
</div>
</div>
)
}