Удален файл с тестовым заданием по системе управления сотрудниками. Обновлены зависимости в package.json и package-lock.json, добавлен новый пакет react-resizable-panels. Внесены изменения в компоненты для улучшения работы боковой панели и отображения дат. Добавлены новые функции для обработки дат в формате DateTime в GraphQL.
This commit is contained in:
@ -27,6 +27,22 @@ export function CategoriesSection() {
|
||||
const [newCategoryName, setNewCategoryName] = useState('')
|
||||
const [editCategoryName, setEditCategoryName] = useState('')
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
try {
|
||||
const date = new Date(dateString)
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'Неизвестно'
|
||||
}
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
})
|
||||
} catch (error) {
|
||||
return 'Неизвестно'
|
||||
}
|
||||
}
|
||||
|
||||
const { data, loading, error, refetch } = useQuery(GET_CATEGORIES)
|
||||
const [createCategory, { loading: creating }] = useMutation(CREATE_CATEGORY)
|
||||
const [updateCategory, { loading: updating }] = useMutation(UPDATE_CATEGORY)
|
||||
@ -266,7 +282,7 @@ export function CategoriesSection() {
|
||||
<div>
|
||||
<h4 className="font-medium text-white">{category.name}</h4>
|
||||
<p className="text-white/60 text-xs">
|
||||
Создано: {new Date(category.createdAt).toLocaleDateString('ru-RU')}
|
||||
Создано: {formatDate(category.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
|
@ -96,13 +96,21 @@ export function UsersSection() {
|
||||
}
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString('ru-RU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
try {
|
||||
const date = new Date(dateString)
|
||||
if (isNaN(date.getTime())) {
|
||||
return 'Неизвестно'
|
||||
}
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
} catch (error) {
|
||||
return 'Неизвестно'
|
||||
}
|
||||
}
|
||||
|
||||
const getInitials = (name?: string, phone?: string) => {
|
||||
|
@ -1,6 +1,7 @@
|
||||
"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'
|
||||
@ -14,13 +15,16 @@ import {
|
||||
Warehouse,
|
||||
Users,
|
||||
Truck,
|
||||
Handshake
|
||||
Handshake,
|
||||
ChevronLeft,
|
||||
ChevronRight
|
||||
} from 'lucide-react'
|
||||
|
||||
export function Sidebar() {
|
||||
const { user, logout } = useAuth()
|
||||
const router = useRouter()
|
||||
const pathname = usePathname()
|
||||
const { isCollapsed, toggleSidebar } = useSidebar()
|
||||
|
||||
const getInitials = () => {
|
||||
const orgName = getOrganizationName()
|
||||
@ -86,6 +90,8 @@ export function Sidebar() {
|
||||
router.push('/partners')
|
||||
}
|
||||
|
||||
|
||||
|
||||
const isSettingsActive = pathname === '/settings'
|
||||
const isMarketActive = pathname.startsWith('/market')
|
||||
const isMessengerActive = pathname.startsWith('/messenger')
|
||||
@ -96,96 +102,149 @@ export function Sidebar() {
|
||||
const isPartnersActive = pathname.startsWith('/partners')
|
||||
|
||||
return (
|
||||
<div className="fixed left-0 top-0 h-full w-56 bg-white/10 backdrop-blur-xl border-r border-white/20 p-3">
|
||||
<div className={`fixed left-4 top-4 bottom-4 ${isCollapsed ? 'w-14' : 'w-72'} 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="flex flex-col h-full">
|
||||
|
||||
{/* Кнопка сворачивания */}
|
||||
<div className={`flex ${isCollapsed ? 'justify-center' : 'justify-end'} ${isCollapsed ? 'mb-2' : 'mb-3'}`}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleSidebar}
|
||||
className={`${isCollapsed ? 'h-7 w-7' : 'h-8 w-8'} text-white/60 hover:text-white hover:bg-white/10 transition-all duration-200`}
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Информация о пользователе */}
|
||||
<Card className="bg-gradient-to-br from-white/15 to-white/5 backdrop-blur border border-white/30 p-4 mb-4 shadow-lg">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative">
|
||||
<Avatar className="h-12 w-12 flex-shrink-0 ring-2 ring-white/20">
|
||||
{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-sm font-semibold">
|
||||
{getInitials()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-green-400 rounded-full border-2 border-white/20"></div>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-white text-sm font-semibold truncate mb-1" title={getOrganizationName()}>
|
||||
{getOrganizationName()}
|
||||
</p>
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-2 h-2 bg-purple-400 rounded-full flex-shrink-0"></div>
|
||||
<p className="text-white/70 text-xs font-medium">
|
||||
{getCabinetType()}
|
||||
{!isCollapsed ? (
|
||||
// Развернутое состояние
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="relative flex-shrink-0">
|
||||
<Avatar className="h-12 w-12 ring-2 ring-white/20">
|
||||
{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-sm font-semibold">
|
||||
{getInitials()}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="absolute -bottom-0.5 -right-0.5 w-3 h-3 bg-green-400 rounded-full border-2 border-white/20"></div>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-white text-sm font-semibold mb-1 break-words" title={getOrganizationName()}>
|
||||
{getOrganizationName()}
|
||||
</p>
|
||||
<div className="flex items-center space-x-1">
|
||||
<div className="w-2 h-2 bg-purple-400 rounded-full flex-shrink-0"></div>
|
||||
<p className="text-white/70 text-xs font-medium">
|
||||
{getCabinetType()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// Свернутое состояние
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="relative mb-2">
|
||||
<Avatar className="h-10 w-10 ring-2 ring-white/20">
|
||||
{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.5 h-2.5 bg-green-400 rounded-full border border-white/20"></div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<p className="text-white text-[10px] font-semibold leading-tight max-w-full break-words"
|
||||
title={getOrganizationName()}
|
||||
style={{ fontSize: '9px', lineHeight: '11px' }}>
|
||||
{getOrganizationName().length > 12
|
||||
? getOrganizationName().substring(0, 12) + '...'
|
||||
: getOrganizationName()
|
||||
}
|
||||
</p>
|
||||
<div className="flex items-center justify-center mt-1">
|
||||
<div className="w-1.5 h-1.5 bg-purple-400 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* Навигация */}
|
||||
<div className="space-y-1 mb-3">
|
||||
<div className="space-y-1 mb-3 flex-1">
|
||||
<Button
|
||||
variant={isMarketActive ? "secondary" : "ghost"}
|
||||
className={`w-full justify-start text-left transition-all duration-200 h-8 text-xs ${
|
||||
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="h-3 w-3 mr-2" />
|
||||
Маркет
|
||||
<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 justify-start text-left transition-all duration-200 h-8 text-xs ${
|
||||
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
|
||||
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-3 w-3 mr-2" />
|
||||
Мессенджер
|
||||
<MessageCircle className="h-4 w-4 flex-shrink-0" />
|
||||
{!isCollapsed && <span className="ml-3">Мессенджер</span>}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={isPartnersActive ? "secondary" : "ghost"}
|
||||
className={`w-full justify-start text-left transition-all duration-200 h-8 text-xs ${
|
||||
className={`w-full ${isCollapsed ? 'justify-center px-2 h-9' : 'justify-start h-10'} text-left transition-all duration-200 text-xs ${
|
||||
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-3 w-3 mr-2" />
|
||||
Партнёры
|
||||
<Handshake className="h-4 w-4 flex-shrink-0" />
|
||||
{!isCollapsed && <span className="ml-3">Партнёры</span>}
|
||||
</Button>
|
||||
|
||||
{/* Услуги - только для фулфилмент центров */}
|
||||
{user?.organization?.type === 'FULFILLMENT' && (
|
||||
<Button
|
||||
variant={isServicesActive ? "secondary" : "ghost"}
|
||||
className={`w-full justify-start text-left transition-all duration-200 h-8 text-xs ${
|
||||
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-3 w-3 mr-2" />
|
||||
Услуги
|
||||
<Wrench className="h-4 w-4 flex-shrink-0" />
|
||||
{!isCollapsed && <span className="ml-3">Услуги</span>}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@ -193,15 +252,16 @@ export function Sidebar() {
|
||||
{user?.organization?.type === 'FULFILLMENT' && (
|
||||
<Button
|
||||
variant={isEmployeesActive ? "secondary" : "ghost"}
|
||||
className={`w-full justify-start text-left transition-all duration-200 h-8 text-xs ${
|
||||
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-3 w-3 mr-2" />
|
||||
Сотрудники
|
||||
<Users className="h-4 w-4 flex-shrink-0" />
|
||||
{!isCollapsed && <span className="ml-3">Сотрудники</span>}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@ -209,15 +269,16 @@ export function Sidebar() {
|
||||
{user?.organization?.type === 'SELLER' && (
|
||||
<Button
|
||||
variant={isSuppliesActive ? "secondary" : "ghost"}
|
||||
className={`w-full justify-start text-left transition-all duration-200 h-8 text-xs ${
|
||||
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-3 w-3 mr-2" />
|
||||
Поставки
|
||||
<Truck className="h-4 w-4 flex-shrink-0" />
|
||||
{!isCollapsed && <span className="ml-3">Поставки</span>}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@ -225,41 +286,44 @@ export function Sidebar() {
|
||||
{user?.organization?.type === 'WHOLESALE' && (
|
||||
<Button
|
||||
variant={isWarehouseActive ? "secondary" : "ghost"}
|
||||
className={`w-full justify-start text-left transition-all duration-200 h-8 text-xs ${
|
||||
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-3 w-3 mr-2" />
|
||||
Склад
|
||||
<Warehouse className="h-4 w-4 flex-shrink-0" />
|
||||
{!isCollapsed && <span className="ml-3">Склад</span>}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant={isSettingsActive ? "secondary" : "ghost"}
|
||||
className={`w-full justify-start text-left transition-all duration-200 h-8 text-xs ${
|
||||
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-3 w-3 mr-2" />
|
||||
Настройки профиля
|
||||
<Settings className="h-4 w-4 flex-shrink-0" />
|
||||
{!isCollapsed && <span className="ml-3">Настройки профиля</span>}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Кнопка выхода */}
|
||||
<div className="flex-1 flex items-end">
|
||||
<div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-start text-white/80 hover:bg-red-500/20 hover:text-red-300 cursor-pointer h-8 text-xs"
|
||||
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-3 w-3 mr-2" />
|
||||
Выйти
|
||||
<LogOut className="h-4 w-4 flex-shrink-0" />
|
||||
{!isCollapsed && <span className="ml-3">Выйти</span>}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -14,6 +14,7 @@ import { Badge } from '@/components/ui/badge'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Sidebar } from './sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import {
|
||||
User,
|
||||
Building2,
|
||||
@ -38,6 +39,7 @@ import { useState, useEffect } from 'react'
|
||||
import Image from 'next/image'
|
||||
|
||||
export function UserSettings() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
const { user } = useAuth()
|
||||
const [updateUserProfile, { loading: isSaving }] = useMutation(UPDATE_USER_PROFILE)
|
||||
const [updateOrganizationByInn, { loading: isUpdatingOrganization }] = useMutation(UPDATE_ORGANIZATION_BY_INN)
|
||||
@ -552,7 +554,7 @@ export function UserSettings() {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Сообщения о сохранении */}
|
||||
{saveMessage && (
|
||||
|
@ -4,6 +4,7 @@ import { useState } from 'react'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import { MarketProducts } from './market-products'
|
||||
import { MarketCategories } from './market-categories'
|
||||
import { MarketRequests } from './market-requests'
|
||||
@ -12,6 +13,7 @@ import { MarketBusiness } from './market-business'
|
||||
import { FavoritesDashboard } from '../favorites/favorites-dashboard'
|
||||
|
||||
export function MarketDashboard() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
const [productsView, setProductsView] = useState<'categories' | 'products' | 'cart' | 'favorites'>('categories')
|
||||
const [selectedCategory, setSelectedCategory] = useState<{ id: string; name: string } | null>(null)
|
||||
|
||||
@ -38,7 +40,7 @@ export function MarketDashboard() {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Основной контент с табами */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
|
@ -1,22 +1,17 @@
|
||||
"use client"
|
||||
|
||||
import React, { useState, useRef, useCallback } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import { MessengerConversations } from './messenger-conversations'
|
||||
import { MessengerChat } from './messenger-chat'
|
||||
import { MessengerEmptyState } from './messenger-empty-state'
|
||||
import { GET_MY_COUNTERPARTIES } from '@/graphql/queries'
|
||||
import {
|
||||
MessageCircle,
|
||||
PanelLeftOpen,
|
||||
PanelLeftClose,
|
||||
Maximize2,
|
||||
Minimize2,
|
||||
Settings
|
||||
} from 'lucide-react'
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'
|
||||
import { MessageCircle } from 'lucide-react'
|
||||
|
||||
interface Organization {
|
||||
id: string
|
||||
@ -31,14 +26,9 @@ interface Organization {
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
type LeftPanelSize = 'compact' | 'normal' | 'wide' | 'hidden'
|
||||
|
||||
export function MessengerDashboard() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
const [selectedCounterparty, setSelectedCounterparty] = useState<string | null>(null)
|
||||
const [leftPanelSize, setLeftPanelSize] = useState<LeftPanelSize>('normal')
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
const [leftPanelWidth, setLeftPanelWidth] = useState(350)
|
||||
const resizeRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
|
||||
const counterparties = counterpartiesData?.myCounterparties || []
|
||||
@ -49,85 +39,13 @@ export function MessengerDashboard() {
|
||||
|
||||
const selectedCounterpartyData = counterparties.find((cp: Organization) => cp.id === selectedCounterparty)
|
||||
|
||||
// Получение ширины для разных размеров панели
|
||||
const getPanelWidth = (size: LeftPanelSize) => {
|
||||
switch (size) {
|
||||
case 'hidden': return 0
|
||||
case 'compact': return 280
|
||||
case 'normal': return 350
|
||||
case 'wide': return 450
|
||||
default: return 350
|
||||
}
|
||||
}
|
||||
|
||||
const currentWidth = leftPanelSize === 'normal' ? leftPanelWidth : getPanelWidth(leftPanelSize)
|
||||
|
||||
// Обработка изменения размера панели
|
||||
const handleMouseDown = useCallback((e: React.MouseEvent) => {
|
||||
setIsResizing(true)
|
||||
e.preventDefault()
|
||||
}, [])
|
||||
|
||||
const handleMouseMove = useCallback((e: MouseEvent) => {
|
||||
if (!isResizing) return
|
||||
|
||||
const newWidth = Math.min(Math.max(280, e.clientX - 56 - 24), 600) // 56px sidebar + 24px padding
|
||||
setLeftPanelWidth(newWidth)
|
||||
setLeftPanelSize('normal')
|
||||
}, [isResizing])
|
||||
|
||||
const handleMouseUp = useCallback(() => {
|
||||
setIsResizing(false)
|
||||
}, [])
|
||||
|
||||
// Добавляем глобальные обработчики для изменения размера
|
||||
React.useEffect(() => {
|
||||
if (isResizing) {
|
||||
document.addEventListener('mousemove', handleMouseMove)
|
||||
document.addEventListener('mouseup', handleMouseUp)
|
||||
document.body.style.cursor = 'col-resize'
|
||||
document.body.style.userSelect = 'none'
|
||||
} else {
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseup', handleMouseUp)
|
||||
document.body.style.cursor = ''
|
||||
document.body.style.userSelect = ''
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove)
|
||||
document.removeEventListener('mouseup', handleMouseUp)
|
||||
document.body.style.cursor = ''
|
||||
document.body.style.userSelect = ''
|
||||
}
|
||||
}, [isResizing, handleMouseMove, handleMouseUp])
|
||||
|
||||
// Переключение размеров панели
|
||||
const togglePanelSize = () => {
|
||||
const sizes: LeftPanelSize[] = ['compact', 'normal', 'wide']
|
||||
const currentIndex = sizes.indexOf(leftPanelSize)
|
||||
const nextIndex = (currentIndex + 1) % sizes.length
|
||||
setLeftPanelSize(sizes[nextIndex])
|
||||
}
|
||||
|
||||
const togglePanelVisibility = () => {
|
||||
setLeftPanelSize(leftPanelSize === 'hidden' ? 'normal' : 'hidden')
|
||||
}
|
||||
|
||||
// Если нет контрагентов, показываем заглушку
|
||||
if (!counterpartiesLoading && counterparties.length === 0) {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
<div className="flex items-center justify-between mb-4 flex-shrink-0">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-white mb-1">Мессенджер</h1>
|
||||
<p className="text-white/70 text-sm">Общение с контрагентами</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Card className="glass-card h-full overflow-hidden p-6">
|
||||
<MessengerEmptyState />
|
||||
@ -142,115 +60,56 @@ export function MessengerDashboard() {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Заголовок с управлением панелями */}
|
||||
<div className="flex items-center justify-between mb-4 flex-shrink-0">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-white mb-1">Мессенджер</h1>
|
||||
<p className="text-white/70 text-sm">Общение с контрагентами</p>
|
||||
</div>
|
||||
|
||||
{/* Управление панелями */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={togglePanelVisibility}
|
||||
className="bg-white/10 hover:bg-white/20 text-white border-white/20 h-8"
|
||||
title={leftPanelSize === 'hidden' ? 'Показать список' : 'Скрыть список'}
|
||||
>
|
||||
{leftPanelSize === 'hidden' ? (
|
||||
<PanelLeftOpen className="h-4 w-4" />
|
||||
) : (
|
||||
<PanelLeftClose className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
|
||||
{leftPanelSize !== 'hidden' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={togglePanelSize}
|
||||
className="bg-white/10 hover:bg-white/20 text-white border-white/20 h-8"
|
||||
title="Изменить размер списка"
|
||||
>
|
||||
{leftPanelSize === 'compact' ? (
|
||||
<Minimize2 className="h-4 w-4" />
|
||||
) : leftPanelSize === 'wide' ? (
|
||||
<Maximize2 className="h-4 w-4" />
|
||||
) : (
|
||||
<Settings className="h-4 w-4" />
|
||||
)}
|
||||
<span className="ml-1 text-xs">
|
||||
{leftPanelSize === 'compact' ? 'Компакт' :
|
||||
leftPanelSize === 'wide' ? 'Широкий' : 'Обычный'}
|
||||
</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Основной контент */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<div className="flex gap-4 h-full">
|
||||
<PanelGroup direction="horizontal" className="h-full">
|
||||
{/* Левая панель - список контрагентов */}
|
||||
{leftPanelSize !== 'hidden' && (
|
||||
<>
|
||||
<Card
|
||||
className="glass-card h-full overflow-hidden p-4 transition-all duration-200 ease-in-out"
|
||||
style={{ width: `${currentWidth}px` }}
|
||||
>
|
||||
<MessengerConversations
|
||||
counterparties={counterparties}
|
||||
loading={counterpartiesLoading}
|
||||
selectedCounterparty={selectedCounterparty}
|
||||
onSelectCounterparty={handleSelectCounterparty}
|
||||
compact={leftPanelSize === 'compact'}
|
||||
/>
|
||||
</Card>
|
||||
<Panel
|
||||
defaultSize={30}
|
||||
minSize={15}
|
||||
maxSize={50}
|
||||
className="pr-2"
|
||||
>
|
||||
<Card className="glass-card h-full overflow-hidden p-4">
|
||||
<MessengerConversations
|
||||
counterparties={counterparties}
|
||||
loading={counterpartiesLoading}
|
||||
selectedCounterparty={selectedCounterparty}
|
||||
onSelectCounterparty={handleSelectCounterparty}
|
||||
compact={false}
|
||||
/>
|
||||
</Card>
|
||||
</Panel>
|
||||
|
||||
{/* Разделитель для изменения размера */}
|
||||
{leftPanelSize === 'normal' && (
|
||||
<div
|
||||
ref={resizeRef}
|
||||
className="w-1 bg-white/10 hover:bg-white/20 cursor-col-resize transition-colors relative group"
|
||||
onMouseDown={handleMouseDown}
|
||||
>
|
||||
<div className="absolute inset-y-0 -inset-x-1 group-hover:bg-white/5 transition-colors" />
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-3 h-8 bg-white/20 rounded-full opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* Разделитель для изменения размера */}
|
||||
<PanelResizeHandle className="w-2 hover:bg-white/10 transition-colors relative group cursor-col-resize">
|
||||
<div className="absolute inset-y-0 left-1/2 transform -translate-x-1/2 w-1 bg-white/10 group-hover:bg-white/20 transition-colors" />
|
||||
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-1 h-8 bg-white/30 rounded-full opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</PanelResizeHandle>
|
||||
|
||||
{/* Правая панель - чат */}
|
||||
<Card className="glass-card h-full overflow-hidden flex-1">
|
||||
{selectedCounterparty && selectedCounterpartyData ? (
|
||||
<MessengerChat counterparty={selectedCounterpartyData} />
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-white/10 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<MessageCircle className="h-8 w-8 text-white/40" />
|
||||
<Panel defaultSize={70} className="pl-2">
|
||||
<Card className="glass-card h-full overflow-hidden">
|
||||
{selectedCounterparty && selectedCounterpartyData ? (
|
||||
<MessengerChat counterparty={selectedCounterpartyData} />
|
||||
) : (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 bg-white/10 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<MessageCircle className="h-8 w-8 text-white/40" />
|
||||
</div>
|
||||
<p className="text-white/60 text-lg mb-2">Выберите контрагента</p>
|
||||
<p className="text-white/40 text-sm">
|
||||
Начните беседу с одним из ваших контрагентов
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-white/60 text-lg mb-2">Выберите контрагента</p>
|
||||
<p className="text-white/40 text-sm">
|
||||
Начните беседу с одним из ваших контрагентов
|
||||
</p>
|
||||
{leftPanelSize === 'hidden' && (
|
||||
<Button
|
||||
onClick={togglePanelVisibility}
|
||||
className="mt-4 bg-purple-600 hover:bg-purple-700 text-white"
|
||||
>
|
||||
Показать список контрагентов
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</Panel>
|
||||
</PanelGroup>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
@ -3,6 +3,7 @@
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import { MarketCounterparties } from '../market/market-counterparties'
|
||||
import { MarketFulfillment } from '../market/market-fulfillment'
|
||||
import { MarketSellers } from '../market/market-sellers'
|
||||
@ -10,10 +11,11 @@ import { MarketLogistics } from '../market/market-logistics'
|
||||
import { MarketWholesale } from '../market/market-wholesale'
|
||||
|
||||
export function PartnersDashboard() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Основной контент с табами */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
|
@ -2,15 +2,17 @@
|
||||
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import { ServicesTab } from './services-tab'
|
||||
import { SuppliesTab } from './supplies-tab'
|
||||
import { LogisticsTab } from './logistics-tab'
|
||||
|
||||
export function ServicesDashboard() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Основной контент с табами */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
|
@ -6,6 +6,7 @@ import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import { ProductForm } from './product-form'
|
||||
import { ProductCard } from './product-card'
|
||||
import { GET_MY_PRODUCTS } from '@/graphql/queries'
|
||||
@ -34,6 +35,7 @@ interface Product {
|
||||
}
|
||||
|
||||
export function WarehouseDashboard() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false)
|
||||
const [editingProduct, setEditingProduct] = useState<Product | null>(null)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
@ -104,7 +106,7 @@ export function WarehouseDashboard() {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="h-full w-full flex flex-col">
|
||||
{/* Заголовок и поиск */}
|
||||
<div className="flex items-center justify-between mb-4 flex-shrink-0">
|
||||
@ -114,14 +116,6 @@ export function WarehouseDashboard() {
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={() => window.open('/admin', '_blank')}
|
||||
variant="outline"
|
||||
className="bg-white/10 hover:bg-white/20 text-white border-white/20 hover:border-white/30"
|
||||
>
|
||||
Управление категориями
|
||||
</Button>
|
||||
|
||||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button
|
||||
|
Reference in New Issue
Block a user