diff --git a/src/components/auth/confirmation-step.tsx b/src/components/auth/confirmation-step.tsx
index 62a5aca..be77696 100644
--- a/src/components/auth/confirmation-step.tsx
+++ b/src/components/auth/confirmation-step.tsx
@@ -5,7 +5,7 @@ import { useState } from 'react'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { AuthLayout } from './auth-layout'
@@ -44,10 +44,7 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
- const { registerFulfillmentOrganization, registerSellerOrganization, registerOrganization } = useAuth()
-
- // 🚀 Feature flag для новой универсальной системы регистрации
- const useNewRegistrationSystem = process.env.NODE_ENV === 'development' || Math.random() < 0.5 // 50% пользователей в production
+ const { registerOrganization, user } = useAuthContext()
// Преобразование типа кабинета в тип организации
const getOrganizationType = (cabinetType: string): 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE' => {
@@ -64,9 +61,40 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
}
const formatPhone = (phone: string) => {
- return phone || '+7 (___) ___-__-__'
+ if (!phone) return '+7 (___) ___-__-__'
+
+ // Очищаем от всех символов кроме цифр
+ const cleaned = phone.replace(/\D/g, '')
+
+ // Форматируем как +7 (XXX) XXX-XX-XX
+ if (cleaned.length === 11 && cleaned.startsWith('7')) {
+ const match = cleaned.match(/^7(\d{3})(\d{3})(\d{2})(\d{2})$/)
+ if (match) {
+ return `+7 (${match[1]}) ${match[2]}-${match[3]}-${match[4]}`
+ }
+ }
+
+ // Если формат не подходит, возвращаем как есть
+ return phone
}
+ // Получаем правильный номер телефона (та же логика что в handleConfirm)
+ const getDisplayPhone = () => {
+ const phoneToUse = data.phone || user?.phone || ''
+ return formatPhone(phoneToUse)
+ }
+
+ console.warn('📌 ConfirmationStep - Component rendered with data:', {
+ phone: data.phone,
+ userPhone: user?.phone,
+ displayPhone: getDisplayPhone(),
+ cabinetType: data.cabinetType,
+ inn: data.inn,
+ hasOrganizationData: !!data.organizationData,
+ referralCode: data.referralCode,
+ partnerCode: data.partnerCode,
+ })
+
const handleConfirm = async () => {
setIsLoading(true)
setError(null)
@@ -76,82 +104,54 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
inn: data.inn,
referralCode: data.referralCode,
partnerCode: data.partnerCode,
- useNewRegistrationSystem,
- hasRegisterFulfillmentOrganization: !!registerFulfillmentOrganization,
- hasRegisterSellerOrganization: !!registerSellerOrganization,
- hasRegisterOrganization: !!registerOrganization,
})
try {
- let result
+
+ // Используем телефон из AuthContext, если data.phone пустой
+ const phoneToUse = data.phone || user?.phone || ''
- if (useNewRegistrationSystem) {
- // 🚀 Новая универсальная система регистрации
- console.warn('🚀 ConfirmationStep - Используем НОВУЮ универсальную систему регистрации')
-
- const organizationType = data.cabinetType === 'seller' ? 'SELLER' : getOrganizationType(data.cabinetType)
-
- const registrationInput = {
- phone: data.phone.replace(/\D/g, ''),
- type: organizationType,
- referralCode: data.referralCode,
- partnerCode: data.partnerCode,
- // Для бизнес-организаций
- ...(organizationType !== 'SELLER' && data.inn && { inn: data.inn }),
- // Для селлеров
- ...(organizationType === 'SELLER' && {
- wbApiKey: data.wbApiKey,
- ozonApiKey: data.ozonApiKey,
- ozonClientId: data.ozonApiValidation?.sellerId, // Используем Client ID из валидации
- }),
- }
-
- console.warn('🚀 ConfirmationStep - Вызов registerOrganization с параметрами:', registrationInput)
- result = await registerOrganization(registrationInput)
- } else {
- // 📜 Старая система регистрации (legacy)
- console.warn('📜 ConfirmationStep - Используем СТАРУЮ систему регистрации')
-
- if (
- (data.cabinetType === 'fulfillment' || data.cabinetType === 'logist' || data.cabinetType === 'wholesale') &&
- data.inn
- ) {
- console.warn('🚨 ConfirmationStep - УСЛОВИЕ ВЫПОЛНЕНО - вызываю registerFulfillmentOrganization:', {
- cabinetType: data.cabinetType,
- hasInn: !!data.inn,
- inn: data.inn,
- organizationType: getOrganizationType(data.cabinetType),
- referralCode: data.referralCode,
- partnerCode: data.partnerCode,
- })
-
- result = await registerFulfillmentOrganization(
- data.phone.replace(/\D/g, ''),
- data.inn,
- getOrganizationType(data.cabinetType),
- data.referralCode,
- data.partnerCode,
- )
- } else if (data.cabinetType === 'seller') {
- result = await registerSellerOrganization({
- phone: data.phone.replace(/\D/g, ''),
- wbApiKey: data.wbApiKey,
- ozonApiKey: data.ozonApiKey,
- referralCode: data.referralCode,
- partnerCode: data.partnerCode,
- })
- }
+ // 🚀 Универсальная система регистрации для всех типов организаций
+ console.warn('🚀 ConfirmationStep - Используем универсальную систему регистрации')
+
+ const organizationType = data.cabinetType === 'seller' ? 'SELLER' : getOrganizationType(data.cabinetType)
+
+ console.warn('🔍 ConfirmationStep - DEBUG phone data:', {
+ dataPhone: data.phone,
+ userPhone: user?.phone,
+ phoneToUse: phoneToUse,
+ cleanedPhone: phoneToUse.replace(/\D/g, ''),
+ phoneLength: phoneToUse.length,
+ isPhoneEmpty: !phoneToUse,
+ })
+
+ const registrationInput = {
+ phone: phoneToUse.replace(/\D/g, ''),
+ type: organizationType,
+ referralCode: data.referralCode,
+ partnerCode: data.partnerCode,
+ // Для бизнес-организаций
+ ...(organizationType !== 'SELLER' && data.inn && { inn: data.inn }),
+ // Для селлеров
+ ...(organizationType === 'SELLER' && {
+ wbApiKey: data.wbApiKey,
+ ozonApiKey: data.ozonApiKey,
+ ozonClientId: data.ozonApiValidation?.sellerId, // Используем Client ID из валидации
+ }),
}
+ console.warn('🚀 ConfirmationStep - Вызов registerOrganization с параметрами:', registrationInput)
+ const result = await registerOrganization(registrationInput)
+
if (result?.success) {
- console.warn(`✅ ConfirmationStep - Регистрация успешна (${useNewRegistrationSystem ? 'NEW' : 'LEGACY'})`)
+ console.warn('✅ ConfirmationStep - Регистрация успешна')
onConfirm()
} else {
- console.warn(`⚠️ ConfirmationStep - Регистрация не успешна (${useNewRegistrationSystem ? 'NEW' : 'LEGACY'}):`, result?.message)
+ console.warn('⚠️ ConfirmationStep - Регистрация не успешна:', result?.message)
setError(result?.message || 'Ошибка при регистрации организации')
}
} catch (error: unknown) {
- console.error(`❌ ConfirmationStep - Registration error (${useNewRegistrationSystem ? 'NEW' : 'LEGACY'}):`, error)
+ console.error('❌ ConfirmationStep - Registration error:', error)
setError('Произошла ошибка при регистрации. Попробуйте еще раз.')
} finally {
setIsLoading(false)
@@ -176,7 +176,7 @@ export function ConfirmationStep({ data, onConfirm, onBack }: ConfirmationStepPr
Телефон:
-
{formatPhone(data.phone)}
+
{getDisplayPhone()}
{
+ console.warn('🔄 RedirectToRegister - Перенаправление на /register')
+ router.replace('/register')
+ }, [router])
+
+ return (
+
+
+
+
Перенаправление на регистрацию...
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/auth/sms-step.tsx b/src/components/auth/sms-step.tsx
index 0c4e7d1..bfe024f 100644
--- a/src/components/auth/sms-step.tsx
+++ b/src/components/auth/sms-step.tsx
@@ -8,8 +8,8 @@ import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { GlassInput } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
+import { useAuthContext } from '@/contexts/AuthContext'
import { SEND_SMS_CODE } from '@/graphql/mutations'
-import { useAuth } from '@/hooks/useAuth'
import { AuthLayout } from './auth-layout'
@@ -27,7 +27,7 @@ export function SmsStep({ phone, onNext, onBack }: SmsStepProps) {
const [error, setError] = useState(null)
const inputRefs = useRef<(HTMLInputElement | null)[]>([])
- const { verifySmsCode } = useAuth()
+ const { verifySmsCode } = useAuthContext()
const [sendSmsCode] = useMutation(SEND_SMS_CODE)
// Автофокус на первое поле при загрузке
@@ -69,41 +69,40 @@ export function SmsStep({ phone, onNext, onBack }: SmsStepProps) {
}
const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault()
- const fullCode = code.join('')
- if (fullCode.length === 4) {
- setIsLoading(true)
- setError(null)
+ try {
+ e.preventDefault()
+ const fullCode = code.join('')
+
+ if (fullCode.length === 4) {
+ console.warn('🔑 SMS: Verifying code', fullCode)
+ setIsLoading(true)
+ setError(null)
- try {
- const cleanPhone = phone.replace(/\D/g, '')
- const formattedPhone = cleanPhone.startsWith('8') ? '7' + cleanPhone.slice(1) : cleanPhone
+ try {
+ const cleanPhone = phone.replace(/\D/g, '')
+ const formattedPhone = cleanPhone.startsWith('8') ? '7' + cleanPhone.slice(1) : cleanPhone
+
+ const result = await verifySmsCode(formattedPhone, fullCode)
- const result = await verifySmsCode(formattedPhone, fullCode)
-
- if (result.success) {
- // Проверяем есть ли у пользователя уже организация
- if (result.user?.organization) {
- // Если организация уже есть, перенаправляем прямо в кабинет
- window.location.href = '/dashboard'
- return
+ if (result.success) {
+ console.warn('🔑 SMS: Success, continuing to next step')
+ onNext(fullCode)
+ } else {
+ setError('Неверный код. Проверьте SMS и попробуйте еще раз.')
+ setCode(['', '', '', ''])
+ inputRefs.current[0]?.focus()
}
-
- // Если организации нет, продолжаем поток регистрации
- onNext(fullCode)
- } else {
- setError('Неверный код. Проверьте SMS и попробуйте еще раз.')
+ } catch (error: unknown) {
+ console.error('🔑 SMS: Verification error:', error)
+ setError('Ошибка проверки кода. Попробуйте еще раз.')
setCode(['', '', '', ''])
inputRefs.current[0]?.focus()
+ } finally {
+ setIsLoading(false)
}
- } catch (error: unknown) {
- console.error('Error verifying SMS code:', error)
- setError('Ошибка проверки кода. Попробуйте еще раз.')
- setCode(['', '', '', ''])
- inputRefs.current[0]?.focus()
- } finally {
- setIsLoading(false)
}
+ } catch (globalError) {
+ console.error('🔑 SMS: Global error:', globalError)
}
}
diff --git a/src/components/dashboard/dashboard-home.tsx b/src/components/dashboard/dashboard-home.tsx
index 199147e..5a63d65 100644
--- a/src/components/dashboard/dashboard-home.tsx
+++ b/src/components/dashboard/dashboard-home.tsx
@@ -5,13 +5,13 @@ import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
import { Sidebar } from './sidebar'
export function DashboardHome() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const router = useRouter()
diff --git a/src/components/dashboard/sidebar/FulfillmentSidebar.tsx b/src/components/dashboard/sidebar/FulfillmentSidebar.tsx
index 91139c0..849d28d 100644
--- a/src/components/dashboard/sidebar/FulfillmentSidebar.tsx
+++ b/src/components/dashboard/sidebar/FulfillmentSidebar.tsx
@@ -3,7 +3,7 @@
import { LogOut } from 'lucide-react'
import { usePathname, useRouter } from 'next/navigation'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
import { NavigationButton } from './core/NavigationButton'
@@ -25,7 +25,7 @@ function FulfillmentSuppliesNotification({ count }: { count: number }) {
}
export function FulfillmentSidebar({ user: propUser }: { user?: any } = {}) {
- const { user: hookUser, logout } = useAuth()
+ const { user: hookUser, logout } = useAuthContext()
// Приоритет: переданный user через props, затем из хука
const user = propUser || hookUser
diff --git a/src/components/dashboard/sidebar/LogistSidebar.tsx b/src/components/dashboard/sidebar/LogistSidebar.tsx
index 62e997b..aa727b1 100644
--- a/src/components/dashboard/sidebar/LogistSidebar.tsx
+++ b/src/components/dashboard/sidebar/LogistSidebar.tsx
@@ -3,7 +3,7 @@
import { LogOut } from 'lucide-react'
import { usePathname, useRouter } from 'next/navigation'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
import { NavigationButton } from './core/NavigationButton'
@@ -14,7 +14,7 @@ import { useSidebarData } from './hooks/useSidebarData'
import { logistNavigation } from './navigations/logist'
export function LogistSidebar({ user: propUser }: { user?: any } = {}) {
- const { user: hookUser, logout } = useAuth()
+ const { user: hookUser, logout } = useAuthContext()
// Приоритет: переданный user через props, затем из хука
const user = propUser || hookUser
diff --git a/src/components/dashboard/sidebar/SellerSidebar.tsx b/src/components/dashboard/sidebar/SellerSidebar.tsx
index 96fe56c..faf961b 100644
--- a/src/components/dashboard/sidebar/SellerSidebar.tsx
+++ b/src/components/dashboard/sidebar/SellerSidebar.tsx
@@ -3,7 +3,7 @@
import { LogOut } from 'lucide-react'
import { usePathname, useRouter } from 'next/navigation'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
import { NavigationButton } from './core/NavigationButton'
@@ -14,7 +14,7 @@ import { useSidebarData } from './hooks/useSidebarData'
import { sellerNavigation } from './navigations/seller'
export function SellerSidebar({ user: propUser }: { user?: any } = {}) {
- const { user: hookUser, logout } = useAuth()
+ const { user: hookUser, logout } = useAuthContext()
// Приоритет: переданный user через props, затем из хука
const user = propUser || hookUser
diff --git a/src/components/dashboard/sidebar/WholesaleSidebar.tsx b/src/components/dashboard/sidebar/WholesaleSidebar.tsx
index 8c015c6..bfbe478 100644
--- a/src/components/dashboard/sidebar/WholesaleSidebar.tsx
+++ b/src/components/dashboard/sidebar/WholesaleSidebar.tsx
@@ -3,7 +3,7 @@
import { LogOut } from 'lucide-react'
import { usePathname, useRouter } from 'next/navigation'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
import { NavigationButton } from './core/NavigationButton'
@@ -25,7 +25,7 @@ function WholesaleOrdersNotification({ count }: { count: number }) {
}
export function WholesaleSidebar({ user: propUser }: { user?: any } = {}) {
- const { user: hookUser, logout } = useAuth()
+ const { user: hookUser, logout } = useAuthContext()
// Приоритет: переданный user через props, затем из хука
const user = propUser || hookUser
diff --git a/src/components/dashboard/sidebar/index.tsx b/src/components/dashboard/sidebar/index.tsx
index ad82c33..b392ae8 100644
--- a/src/components/dashboard/sidebar/index.tsx
+++ b/src/components/dashboard/sidebar/index.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { FulfillmentSidebar } from './FulfillmentSidebar'
import { LogistSidebar } from './LogistSidebar'
@@ -15,12 +15,12 @@ declare global {
export function Sidebar({
isRootInstance = false,
- user: propUser
+ user: propUser,
}: {
isRootInstance?: boolean,
- user?: any
+ user?: any,
} = {}) {
- const { user: hookUser } = useAuth()
+ const { user: hookUser } = useAuthContext()
// Приоритет: переданный user через props, затем из хука
const user = propUser || hookUser
@@ -32,7 +32,7 @@ export function Sidebar({
hasHookUser: !!hookUser,
userPhone: user?.phone,
organizationType: user?.organization?.type,
- willRender: !!user?.organization?.type
+ willRender: !!user?.organization?.type,
})
// Диагностика почему user пустой
diff --git a/src/components/dashboard/user-settings.tsx b/src/components/dashboard/user-settings.tsx
index 4d07c00..4515574 100644
--- a/src/components/dashboard/user-settings.tsx
+++ b/src/components/dashboard/user-settings.tsx
@@ -31,9 +31,9 @@ import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { useAuthContext } from '@/contexts/AuthContext'
import { UPDATE_USER_PROFILE, UPDATE_ORGANIZATION_BY_INN, ADD_MARKETPLACE_API_KEY } from '@/graphql/mutations'
import { GET_ME } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
import { useSidebar } from '@/hooks/useSidebar'
import { apolloClient } from '@/lib/apollo-client'
import { formatPhone } from '@/lib/utils'
@@ -43,7 +43,7 @@ import { Sidebar } from './sidebar'
export function UserSettings() {
const { getSidebarMargin } = useSidebar()
- const { user, updateUser } = useAuth()
+ const { user, updateUser } = useAuthContext()
const [updateUserProfile, { loading: isSaving }] = useMutation(UPDATE_USER_PROFILE)
const [updateOrganizationByInn, { loading: isUpdatingOrganization }] = useMutation(UPDATE_ORGANIZATION_BY_INN)
const [isEditing, setIsEditing] = useState(false)
diff --git a/src/components/dashboard/user-settings.tsx.backup b/src/components/dashboard/user-settings.tsx.backup
deleted file mode 100644
index f8febf8..0000000
--- a/src/components/dashboard/user-settings.tsx.backup
+++ /dev/null
@@ -1,1579 +0,0 @@
-'use client'
-
-import { useMutation } from '@apollo/client'
-import {
- User,
- Building2,
- Phone,
- Mail,
- MapPin,
- CreditCard,
- Key,
- Edit3,
- CheckCircle,
- AlertTriangle,
- MessageCircle,
- Save,
- RefreshCw,
- Calendar,
- Settings,
- Camera,
-} from 'lucide-react'
-import Image from 'next/image'
-import { useState, useEffect, useRef } from 'react'
-
-import { Alert, AlertDescription } from '@/components/ui/alert'
-import { Avatar, AvatarFallback } from '@/components/ui/avatar'
-import { Badge } from '@/components/ui/badge'
-import { Button } from '@/components/ui/button'
-import { Card } from '@/components/ui/card'
-import { Input } from '@/components/ui/input'
-import { Label } from '@/components/ui/label'
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
-import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
-import { UPDATE_USER_PROFILE, UPDATE_ORGANIZATION_BY_INN } from '@/graphql/mutations'
-import { GET_ME } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
-import { useSidebar } from '@/hooks/useSidebar'
-import { apolloClient } from '@/lib/apollo-client'
-import { formatPhone } from '@/lib/utils'
-import S3Service from '@/services/s3-service'
-
-import { Sidebar } from './sidebar'
-
-export function UserSettings() {
- const { getSidebarMargin } = useSidebar()
- const { user, updateUser } = useAuth()
- const [updateUserProfile, { loading: isSaving }] = useMutation(UPDATE_USER_PROFILE)
- const [updateOrganizationByInn, { loading: isUpdatingOrganization }] = useMutation(UPDATE_ORGANIZATION_BY_INN)
- const [isEditing, setIsEditing] = useState(false)
- const [saveMessage, setSaveMessage] = useState<{
- type: 'success' | 'error'
- text: string
- } | null>(null)
- const [isUploadingAvatar, setIsUploadingAvatar] = useState(false)
- const [localAvatarUrl, setLocalAvatarUrl] = useState(null)
- const phoneInputRef = useRef(null)
- const whatsappInputRef = useRef(null)
-
- // Инициализируем данные из пользователя и организации
- const [formData, setFormData] = useState({
- // Контактные данные организации
- orgPhone: '', // телефон организации, не пользователя
- managerName: '',
- telegram: '',
- whatsapp: '',
- email: '',
-
- // Организация - данные могут быть заполнены из DaData
- orgName: '',
- address: '',
-
- // Юридические данные - могут быть заполнены из DaData
- fullName: '',
- inn: '',
- ogrn: '',
- registrationPlace: '',
-
- // Финансовые данные - требуют ручного заполнения
- bankName: '',
- bik: '',
- accountNumber: '',
- corrAccount: '',
-
- // API ключи маркетплейсов
- wildberriesApiKey: '',
- ozonApiKey: '',
-
- // Рынок для поставщиков
- market: '',
- })
-
- // Загружаем данные организации при монтировании компонента
- useEffect(() => {
- if (user?.organization) {
- const org = user.organization
-
- // Извлекаем первый телефон из phones JSON
- let orgPhone = ''
- if (org.phones && Array.isArray(org.phones) && org.phones.length > 0) {
- orgPhone = org.phones[0].value || org.phones[0] || ''
- } else if (org.phones && typeof org.phones === 'object') {
- const phoneValues = Object.values(org.phones)
- if (phoneValues.length > 0) {
- orgPhone = String(phoneValues[0])
- }
- }
-
- // Извлекаем email из emails JSON
- let email = ''
- if (org.emails && Array.isArray(org.emails) && org.emails.length > 0) {
- email = org.emails[0].value || org.emails[0] || ''
- } else if (org.emails && typeof org.emails === 'object') {
- const emailValues = Object.values(org.emails)
- if (emailValues.length > 0) {
- email = String(emailValues[0])
- }
- }
-
- // Извлекаем дополнительные данные из managementPost (JSON)
- let customContacts: {
- managerName?: string
- telegram?: string
- whatsapp?: string
- bankDetails?: {
- bankName?: string
- bik?: string
- accountNumber?: string
- corrAccount?: string
- }
- } = {}
- try {
- if (org.managementPost && typeof org.managementPost === 'string') {
- // Проверяем, что строка начинается с { или [, иначе это не JSON
- if (org.managementPost.trim().startsWith('{') || org.managementPost.trim().startsWith('[')) {
- customContacts = JSON.parse(org.managementPost)
- }
- }
- } catch {
- // Игнорируем ошибки парсинга
- }
-
- setFormData({
- orgPhone: orgPhone || '+7',
- managerName: user?.managerName || '',
- telegram: customContacts?.telegram || '',
- whatsapp: customContacts?.whatsapp || '',
- email: email,
- orgName: org.name || '',
- address: org.address || '',
- fullName: org.fullName || '',
- inn: org.inn || '',
- ogrn: org.ogrn || '',
- registrationPlace: org.address || '',
- bankName: customContacts?.bankDetails?.bankName || '',
- bik: customContacts?.bankDetails?.bik || '',
- accountNumber: customContacts?.bankDetails?.accountNumber || '',
- corrAccount: customContacts?.bankDetails?.corrAccount || '',
- wildberriesApiKey: '',
- ozonApiKey: '',
- market: (org as any).market || 'none',
- })
- }
- }, [user])
-
- const getInitials = () => {
- const orgName = user?.organization?.name || user?.organization?.fullName
- if (orgName) {
- return orgName.charAt(0).toUpperCase()
- }
- return user?.phone ? user.phone.slice(-2).toUpperCase() : 'О'
- }
-
- const getCabinetTypeName = () => {
- if (!user?.organization?.type) return 'Не указан'
-
- switch (user.organization.type) {
- case 'FULFILLMENT':
- return 'Фулфилмент'
- case 'SELLER':
- return 'Селлер'
- case 'LOGIST':
- return 'Логистика'
- case 'WHOLESALE':
- return 'Поставщик'
- default:
- return 'Не указан'
- }
- }
-
- // Обновленная функция для проверки заполненности профиля
- const checkProfileCompleteness = () => {
- // Базовые поля (обязательные для всех)
- const baseFields = [
- {
- field: 'orgPhone',
- label: 'Телефон организации',
- value: formData.orgPhone,
- },
- {
- field: 'managerName',
- label: 'Имя управляющего',
- value: formData.managerName,
- },
- { field: 'email', label: 'Email', value: formData.email },
- ]
-
- // Дополнительные поля в зависимости от типа кабинета
- const additionalFields = []
- if (
- user?.organization?.type === 'FULFILLMENT' ||
- user?.organization?.type === 'LOGIST' ||
- user?.organization?.type === 'WHOLESALE' ||
- user?.organization?.type === 'SELLER'
- ) {
- // Финансовые данные - всегда обязательны для всех типов кабинетов
- additionalFields.push(
- {
- field: 'bankName',
- label: 'Название банка',
- value: formData.bankName,
- },
- { field: 'bik', label: 'БИК', value: formData.bik },
- {
- field: 'accountNumber',
- label: 'Расчетный счет',
- value: formData.accountNumber,
- },
- {
- field: 'corrAccount',
- label: 'Корр. счет',
- value: formData.corrAccount,
- },
- )
- }
-
- const allRequiredFields = [...baseFields, ...additionalFields]
- const filledRequiredFields = allRequiredFields.filter((field) => field.value && field.value.trim() !== '').length
-
- // Подсчитываем бонусные баллы за автоматически заполненные поля
- let autoFilledFields = 0
- let totalAutoFields = 0
-
- // Номер телефона пользователя для авторизации (не считаем в процентах заполненности)
- // Телефон организации учитывается отдельно как обычное поле
-
- // Данные организации из DaData (если есть ИНН)
- if (formData.inn || user?.organization?.inn) {
- totalAutoFields += 5 // ИНН + название + адрес + полное название + ОГРН
-
- if (formData.inn || user?.organization?.inn) autoFilledFields += 1 // ИНН
- if (formData.orgName || user?.organization?.name) autoFilledFields += 1 // Название
- if (formData.address || user?.organization?.address) autoFilledFields += 1 // Адрес
- if (formData.fullName || user?.organization?.fullName) autoFilledFields += 1 // Полное название
- if (formData.ogrn || user?.organization?.ogrn) autoFilledFields += 1 // ОГРН
- }
-
- // Место регистрации
- if (formData.registrationPlace || user?.organization?.registrationDate) {
- autoFilledFields += 1
- totalAutoFields += 1
- }
-
- const totalPossibleFields = allRequiredFields.length + totalAutoFields
- const totalFilledFields = filledRequiredFields + autoFilledFields
-
- const percentage = totalPossibleFields > 0 ? Math.round((totalFilledFields / totalPossibleFields) * 100) : 0
- const missingFields = allRequiredFields
- .filter((field) => !field.value || field.value.trim() === '')
- .map((field) => field.label)
-
- return { percentage, missingFields }
- }
-
- const profileStatus = checkProfileCompleteness()
- const isIncomplete = profileStatus.percentage < 100
-
- const handleAvatarUpload = async (event: React.ChangeEvent) => {
- const file = event.target.files?.[0]
- if (!file || !user?.id) return
-
- setIsUploadingAvatar(true)
- setSaveMessage(null)
-
- try {
- const avatarUrl = await S3Service.uploadAvatar(file, user.id)
-
- // Сразу обновляем локальное состояние для мгновенного отображения
- setLocalAvatarUrl(avatarUrl)
-
- // Обновляем аватар пользователя через GraphQL
- const result = await updateUserProfile({
- variables: {
- input: {
- avatar: avatarUrl,
- },
- },
- update: (cache, { data }: { data?: any }) => {
- if (data?.updateUserProfile?.success) {
- // Обновляем кеш Apollo Client
- try {
- const existingData: any = cache.readQuery({ query: GET_ME })
- if (existingData?.me) {
- cache.writeQuery({
- query: GET_ME,
- data: {
- me: {
- ...existingData.me,
- avatar: avatarUrl,
- },
- },
- })
- }
- } catch {
- // Игнорируем ошибки обновления кеша
- }
- }
- },
- })
-
- if (result.data?.updateUserProfile?.success) {
- setSaveMessage({ type: 'success', text: 'Аватар успешно обновлен!' })
-
- // Обновляем локальное состояние в useAuth для мгновенного отображения в сайдбаре
- updateUser({ avatar: avatarUrl })
-
- // Принудительно обновляем Apollo Client кеш
- await apolloClient.refetchQueries({
- include: [GET_ME],
- })
-
- // Очищаем input файла
- if (event.target) {
- event.target.value = ''
- }
-
- // Очищаем сообщение через 3 секунды
- setTimeout(() => {
- setSaveMessage(null)
- }, 3000)
- } else {
- throw new Error(result.data?.updateUserProfile?.message || 'Failed to update avatar')
- }
- } catch (error) {
- console.error('Error uploading avatar:', error)
- // Сбрасываем локальное состояние при ошибке
- setLocalAvatarUrl(null)
- const errorMessage = error instanceof Error ? error.message : 'Ошибка при загрузке аватара'
- setSaveMessage({ type: 'error', text: errorMessage })
- // Очищаем сообщение об ошибке через 5 секунд
- setTimeout(() => {
- setSaveMessage(null)
- }, 5000)
- } finally {
- setIsUploadingAvatar(false)
- }
- }
-
- // Функции для валидации и масок
- const validateEmail = (email: string) => {
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
- return emailRegex.test(email)
- }
-
- const formatPhoneInput = (value: string, isOptional: boolean = false) => {
- // Убираем все нецифровые символы
- const digitsOnly = value.replace(/\D/g, '')
-
- // Если строка пустая
- if (!digitsOnly) {
- // Для необязательных полей возвращаем пустую строку
- if (isOptional) return ''
- // Для обязательных полей возвращаем +7
- return '+7'
- }
-
- // Если пользователь ввел первую цифру не 7, добавляем 7 перед ней
- let cleaned = digitsOnly
- if (!cleaned.startsWith('7')) {
- cleaned = '7' + cleaned
- }
-
- // Ограничиваем до 11 цифр (7 + 10 цифр номера)
- cleaned = cleaned.slice(0, 11)
-
- // Форматируем в зависимости от длины
- if (cleaned.length <= 1) return isOptional && cleaned === '7' ? '' : '+7'
- if (cleaned.length <= 4) return `+7 (${cleaned.slice(1)}`
- if (cleaned.length <= 7) return `+7 (${cleaned.slice(1, 4)}) ${cleaned.slice(4)}`
- if (cleaned.length <= 9) return `+7 (${cleaned.slice(1, 4)}) ${cleaned.slice(4, 7)}-${cleaned.slice(7)}`
- if (cleaned.length <= 11)
- return `+7 (${cleaned.slice(1, 4)}) ${cleaned.slice(4, 7)}-${cleaned.slice(7, 9)}-${cleaned.slice(9)}`
-
- return `+7 (${cleaned.slice(1, 4)}) ${cleaned.slice(4, 7)}-${cleaned.slice(7, 9)}-${cleaned.slice(9, 11)}`
- }
-
- const handlePhoneInputChange = (
- field: string,
- value: string,
- inputRef: React.RefObject,
- isOptional: boolean = false,
- ) => {
- const currentInput = inputRef?.current
- const currentCursorPosition = currentInput?.selectionStart || 0
- const currentValue = (formData[field as keyof typeof formData] as string) || ''
-
- // Для необязательных полей разрешаем пустое значение
- if (isOptional && value.length < 2) {
- const formatted = formatPhoneInput(value, true)
- setFormData((prev) => ({ ...prev, [field]: formatted }))
- return
- }
-
- // Для обязательных полей если пользователь пытается удалить +7, предотвращаем это
- if (!isOptional && value.length < 2) {
- value = '+7'
- }
-
- const formatted = formatPhoneInput(value, isOptional)
- setFormData((prev) => ({ ...prev, [field]: formatted }))
-
- // Вычисляем новую позицию курсора
- if (currentInput) {
- setTimeout(() => {
- let newCursorPosition = currentCursorPosition
-
- // Если длина увеличилась (добавили цифру), передвигаем курсор
- if (formatted.length > currentValue.length) {
- newCursorPosition = currentCursorPosition + (formatted.length - currentValue.length)
- }
- // Если длина уменьшилась (удалили цифру), оставляем курсор на месте или сдвигаем немного
- else if (formatted.length < currentValue.length) {
- newCursorPosition = Math.min(currentCursorPosition, formatted.length)
- }
-
- // Не позволяем курсору находиться перед +7
- newCursorPosition = Math.max(newCursorPosition, 2)
-
- // Ограничиваем курсор длиной строки
- newCursorPosition = Math.min(newCursorPosition, formatted.length)
-
- currentInput.setSelectionRange(newCursorPosition, newCursorPosition)
- }, 0)
- }
- }
-
- const formatTelegram = (value: string) => {
- // Убираем все символы кроме букв, цифр, _ и @
- let cleaned = value.replace(/[^a-zA-Z0-9_@]/g, '')
-
- // Убираем лишние символы @
- cleaned = cleaned.replace(/@+/g, '@')
-
- // Если есть символы после удаления @ и строка не начинается с @, добавляем @
- if (cleaned && !cleaned.startsWith('@')) {
- cleaned = '@' + cleaned
- }
-
- // Ограничиваем длину (максимум 32 символа для Telegram)
- if (cleaned.length > 33) {
- cleaned = cleaned.substring(0, 33)
- }
-
- return cleaned
- }
-
- const validateName = (name: string) => {
- return /^[а-яёА-ЯЁa-zA-Z\s-]+$/.test(name) && name.trim().length >= 2
- }
-
- const handleInputChange = (field: string, value: string) => {
- let processedValue = value
-
- // Применяем маски и валидации
- switch (field) {
- case 'orgPhone':
- case 'whatsapp':
- processedValue = formatPhoneInput(value)
- break
- case 'telegram':
- processedValue = formatTelegram(value)
- break
- case 'email':
- // Для email не применяем маску, только валидацию при потере фокуса
- break
- case 'managerName':
- // Разрешаем только буквы, пробелы и дефисы
- processedValue = value.replace(/[^а-яёА-ЯЁa-zA-Z\s-]/g, '')
- break
- }
-
- setFormData((prev) => ({ ...prev, [field]: processedValue }))
- }
-
- // Функции для проверки ошибок
- const getFieldError = (field: string, value: string) => {
- if (!isEditing || !value.trim()) return null
-
- switch (field) {
- case 'email':
- return !validateEmail(value) ? 'Неверный формат email' : null
- case 'managerName':
- return !validateName(value) ? 'Только буквы, пробелы и дефисы' : null
- case 'orgPhone':
- case 'whatsapp':
- const cleaned = value.replace(/\D/g, '')
- return cleaned.length !== 11 ? 'Неверный формат телефона' : null
- case 'telegram':
- // Проверяем что после @ есть минимум 5 символов
- const usernameLength = value.startsWith('@') ? value.length - 1 : value.length
- return usernameLength < 5 ? 'Минимум 5 символов после @' : null
- case 'inn':
- // Игнорируем автоматически сгенерированные ИНН селлеров
- if (value.startsWith('SELLER_')) {
- return null
- }
- const innCleaned = value.replace(/\D/g, '')
- if (innCleaned.length !== 10 && innCleaned.length !== 12) {
- return 'ИНН должен содержать 10 или 12 цифр'
- }
- return null
- case 'bankName':
- return value.trim().length < 3 ? 'Минимум 3 символа' : null
- case 'bik':
- const bikCleaned = value.replace(/\D/g, '')
- return bikCleaned.length !== 9 ? 'БИК должен содержать 9 цифр' : null
- case 'accountNumber':
- const accountCleaned = value.replace(/\D/g, '')
- return accountCleaned.length !== 20 ? 'Расчетный счет должен содержать 20 цифр' : null
- case 'corrAccount':
- const corrCleaned = value.replace(/\D/g, '')
- return corrCleaned.length !== 20 ? 'Корр. счет должен содержать 20 цифр' : null
- default:
- return null
- }
- }
-
- // Проверка наличия изменений в форме
- const hasFormChanges = () => {
- if (!user?.organization) return false
-
- const org = user.organization
-
- // Извлекаем текущий телефон из organization.phones
- let currentOrgPhone = '+7'
- if (org.phones && Array.isArray(org.phones) && org.phones.length > 0) {
- currentOrgPhone = org.phones[0].value || org.phones[0] || '+7'
- }
-
- // Извлекаем текущий email из organization.emails
- let currentEmail = ''
- if (org.emails && Array.isArray(org.emails) && org.emails.length > 0) {
- currentEmail = org.emails[0].value || org.emails[0] || ''
- }
-
- // Извлекаем дополнительные данные из managementPost
- let customContacts: any = {}
- try {
- if (org.managementPost && typeof org.managementPost === 'string') {
- // Проверяем, что строка начинается с { или [, иначе это не JSON
- if (org.managementPost.trim().startsWith('{') || org.managementPost.trim().startsWith('[')) {
- customContacts = JSON.parse(org.managementPost)
- }
- }
- } catch {
- // ignore parse errors
- }
-
- // Нормализуем значения для сравнения
- const normalizeValue = (value: string | null | undefined) => value || ''
- const normalizeMarketValue = (value: string | null | undefined) => value || 'none'
-
- // Проверяем изменения в полях
- const changes = [
- normalizeValue(formData.orgPhone) !== normalizeValue(currentOrgPhone),
- normalizeValue(formData.managerName) !== normalizeValue(user?.managerName),
- normalizeValue(formData.telegram) !== normalizeValue(customContacts?.telegram),
- normalizeValue(formData.whatsapp) !== normalizeValue(customContacts?.whatsapp),
- normalizeValue(formData.email) !== normalizeValue(currentEmail),
- normalizeMarketValue(formData.market) !== normalizeMarketValue((org as any).market),
- normalizeValue(formData.bankName) !== normalizeValue(customContacts?.bankDetails?.bankName),
- normalizeValue(formData.bik) !== normalizeValue(customContacts?.bankDetails?.bik),
- normalizeValue(formData.accountNumber) !== normalizeValue(customContacts?.bankDetails?.accountNumber),
- normalizeValue(formData.corrAccount) !== normalizeValue(customContacts?.bankDetails?.corrAccount),
- ]
-
- const hasChanges = changes.some((changed) => changed)
- return hasChanges
- }
-
- // Проверка наличия ошибок валидации
- const hasValidationErrors = () => {
- const fields = [
- 'orgPhone',
- 'managerName',
- 'telegram',
- 'whatsapp',
- 'email',
- 'inn',
- 'bankName',
- 'bik',
- 'accountNumber',
- 'corrAccount',
- ]
-
- // Проверяем ошибки валидации только в заполненных полях
- const hasErrors = fields.some((field) => {
- const value = formData[field as keyof typeof formData]
- // Проверяем ошибки только для заполненных полей
- if (!value || !value.trim()) return false
-
- const error = getFieldError(field, value)
- return error !== null
- })
-
- // Убираем проверку обязательных полей - пользователь может заполнять постепенно
- return hasErrors
- }
-
- const handleSave = async () => {
- // Сброс предыдущих сообщений
- setSaveMessage(null)
-
- try {
- // Проверяем, изменился ли ИНН и нужно ли обновить данные организации
- const currentInn = formData.inn || user?.organization?.inn || ''
- const originalInn = user?.organization?.inn || ''
- const innCleaned = currentInn.replace(/\D/g, '')
- const originalInnCleaned = originalInn.replace(/\D/g, '')
-
- // Если ИНН изменился и валиден, сначала обновляем данные организации
- if (innCleaned !== originalInnCleaned && (innCleaned.length === 10 || innCleaned.length === 12)) {
- setSaveMessage({
- type: 'success',
- text: 'Обновляем данные организации...',
- })
-
- const orgResult = await updateOrganizationByInn({
- variables: { inn: innCleaned },
- })
-
- if (!orgResult.data?.updateOrganizationByInn?.success) {
- setSaveMessage({
- type: 'error',
- text: orgResult.data?.updateOrganizationByInn?.message || 'Ошибка при обновлении данных организации',
- })
- return
- }
-
- setSaveMessage({
- type: 'success',
- text: 'Данные организации обновлены. Сохраняем профиль...',
- })
- }
-
- // Подготавливаем только заполненные поля для отправки
- const inputData: {
- orgPhone?: string
- managerName?: string
- telegram?: string
- whatsapp?: string
- email?: string
- bankName?: string
- bik?: string
- accountNumber?: string
- corrAccount?: string
- market?: string
- } = {}
-
- // orgName больше не редактируется - устанавливается только при регистрации
- if (formData.orgPhone?.trim()) inputData.orgPhone = formData.orgPhone.trim()
- if (formData.managerName?.trim()) inputData.managerName = formData.managerName.trim()
- if (formData.telegram?.trim()) inputData.telegram = formData.telegram.trim()
- if (formData.whatsapp?.trim()) inputData.whatsapp = formData.whatsapp.trim()
- if (formData.email?.trim()) inputData.email = formData.email.trim()
- if (formData.bankName?.trim()) inputData.bankName = formData.bankName.trim()
- if (formData.bik?.trim()) inputData.bik = formData.bik.trim()
- if (formData.accountNumber?.trim()) inputData.accountNumber = formData.accountNumber.trim()
- if (formData.corrAccount?.trim()) inputData.corrAccount = formData.corrAccount.trim()
- if (formData.market) inputData.market = formData.market
-
- const result = await updateUserProfile({
- variables: {
- input: inputData,
- },
- })
-
- if (result.data?.updateUserProfile?.success) {
- setSaveMessage({
- type: 'success',
- text: 'Профиль успешно сохранен! Обновляем страницу...',
- })
-
- // Простое обновление страницы после успешного сохранения
- setTimeout(() => {
- window.location.reload()
- }, 1000)
- } else {
- setSaveMessage({
- type: 'error',
- text: result.data?.updateUserProfile?.message || 'Ошибка при сохранении профиля',
- })
- }
- } catch (error) {
- console.error('Error saving profile:', error)
- setSaveMessage({ type: 'error', text: 'Ошибка при сохранении профиля' })
- }
- }
-
- const formatDate = (dateString?: string) => {
- if (!dateString) return ''
- try {
- let date: Date
-
- // Проверяем, является ли строка числом (Unix timestamp)
- if (/^\d+$/.test(dateString)) {
- // Если это Unix timestamp в миллисекундах
- const timestamp = parseInt(dateString, 10)
- date = new Date(timestamp)
- } else {
- // Обычная строка даты
- date = new Date(dateString)
- }
-
- if (isNaN(date.getTime())) {
- return 'Неверная дата'
- }
-
- return date.toLocaleDateString('ru-RU', {
- year: 'numeric',
- month: 'long',
- day: 'numeric',
- })
- } catch {
- return 'Ошибка даты'
- }
- }
-
- return (
-
-
-
-
- {/* Сообщения о сохранении */}
- {saveMessage && (
-
-
- {saveMessage.text}
-
-
- )}
-
- {/* Основной контент с вкладками - заполняет оставшееся пространство */}
-
-
-
-
-
- Профиль
-
-
-
- Организация
-
- {(user?.organization?.type === 'FULFILLMENT' ||
- user?.organization?.type === 'LOGIST' ||
- user?.organization?.type === 'WHOLESALE' ||
- user?.organization?.type === 'SELLER') && (
-
-
- Финансы
-
- )}
- {user?.organization?.type === 'SELLER' && (
-
-
- API
-
- )}
- {user?.organization?.type !== 'SELLER' && (
-
-
- Инструменты
-
- )}
-
-
- {/* Профиль пользователя */}
-
-
- {/* Заголовок вкладки с прогрессом и кнопками */}
-
-
-
-
-
Профиль пользователя
-
Личная информация и контактные данные
-
-
-
- {/* Компактный индикатор прогресса */}
-
-
- {profileStatus.percentage}%
-
-
- {isIncomplete ? (
- <>Заполнено {profileStatus.percentage}% профиля>
- ) : (
- <>Профиль полностью заполнен>
- )}
-
-
-
- {isEditing ? (
- <>
-
-
- >
- ) : (
-
- )}
-
-
-
-
-
- {localAvatarUrl || user?.avatar ? (
-
- ) : (
- {getInitials()}
- )}
-
-
-
-
-
-
-
-
- {user?.organization?.name || user?.organization?.fullName || 'Пользователь'}
-
-
- {getCabinetTypeName()}
-
-
- Авторизован по номеру: {formatPhone(user?.phone || '')}
-
- {user?.createdAt && (
-
-
- Дата регистрации: {formatDate(user.createdAt)}
-
- )}
-
-
-
-
-
-
-
-
-
handlePhoneInputChange('orgPhone', e.target.value, phoneInputRef)}
- onKeyDown={(e) => {
- // Предотвращаем удаление +7
- if (
- (e.key === 'Backspace' || e.key === 'Delete') &&
- (phoneInputRef.current?.selectionStart || 0) <= 2
- ) {
- e.preventDefault()
- }
- }}
- placeholder="+7 (999) 999-99-99"
- readOnly={!isEditing}
- className={`glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70 ${
- getFieldError('orgPhone', formData.orgPhone) ? 'border-red-400' : ''
- }`}
- />
- {getFieldError('orgPhone', formData.orgPhone) && (
-
-
- {getFieldError('orgPhone', formData.orgPhone)}
-
- )}
-
-
-
-
-
handleInputChange('managerName', e.target.value)}
- placeholder="Иван Иванов"
- readOnly={!isEditing}
- className={`glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70 ${
- getFieldError('managerName', formData.managerName) ? 'border-red-400' : ''
- }`}
- />
- {getFieldError('managerName', formData.managerName) && (
-
-
- {getFieldError('managerName', formData.managerName)}
-
- )}
-
-
-
-
-
-
-
handleInputChange('telegram', e.target.value)}
- placeholder="@username"
- readOnly={!isEditing}
- className={`glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70 ${
- getFieldError('telegram', formData.telegram) ? 'border-red-400' : ''
- }`}
- />
- {getFieldError('telegram', formData.telegram) && (
-
-
- {getFieldError('telegram', formData.telegram)}
-
- )}
-
-
-
-
-
handlePhoneInputChange('whatsapp', e.target.value, whatsappInputRef, true)}
- onKeyDown={(_e) => {
- // Для WhatsApp разрешаем полное удаление (поле необязательное)
- // Никаких ограничений на удаление
- }}
- placeholder="Необязательно"
- readOnly={!isEditing}
- className={`glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70 ${
- getFieldError('whatsapp', formData.whatsapp) ? 'border-red-400' : ''
- }`}
- />
- {getFieldError('whatsapp', formData.whatsapp) && (
-
-
- {getFieldError('whatsapp', formData.whatsapp)}
-
- )}
-
-
-
-
-
handleInputChange('email', e.target.value)}
- placeholder="example@company.com"
- readOnly={!isEditing}
- className={`glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70 ${
- getFieldError('email', formData.email) ? 'border-red-400' : ''
- }`}
- />
- {getFieldError('email', formData.email) && (
-
-
- {getFieldError('email', formData.email)}
-
- )}
-
-
-
-
-
-
- {/* Организация и юридические данные */}
-
-
- {/* Заголовок вкладки с кнопками */}
-
-
-
-
-
Данные организации
-
Юридическая информация и реквизиты
-
-
-
- {(formData.inn || user?.organization?.inn) && (
-
-
- Проверено
-
- )}
-
- {isEditing ? (
- <>
-
-
- >
- ) : (
-
- )}
-
-
-
- {/* Общая подпись про реестр */}
-
-
-
- При сохранении с измененным ИНН мы автоматически обновляем все остальные данные из федерального
- реестра
-
-
-
-
- {/* Названия */}
-
-
-
-
handleInputChange('orgName', e.target.value)}
- placeholder={
- user?.organization?.type === 'SELLER' ? 'Название магазина' : 'Название организации'
- }
- readOnly={true}
- className="glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70"
- />
- {user?.organization?.type === 'SELLER' ? (
-
- Название устанавливается при регистрации кабинета и не может быть изменено.
-
- ) : (
-
- Автоматически заполняется из федерального реестра при указании ИНН.
-
- )}
-
-
-
-
-
-
-
-
- {/* Адреса */}
-
-
- {/* ИНН, ОГРН, КПП */}
-
-
-
-
{
- handleInputChange('inn', e.target.value)
- }}
- placeholder="Введите ИНН организации"
- readOnly={!isEditing}
- disabled={isUpdatingOrganization}
- className={`glass-input text-white placeholder:text-white/40 h-10 ${
- !isEditing ? 'read-only:opacity-70' : ''
- } ${
- getFieldError('inn', formData.inn) ? 'border-red-400' : ''
- } ${isUpdatingOrganization ? 'opacity-50' : ''}`}
- />
- {getFieldError('inn', formData.inn) && (
-
{getFieldError('inn', formData.inn)}
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Руководитель и статус */}
-
-
-
-
-
- {user?.organization?.managementName
- ? 'Данные из федерального реестра'
- : 'Автоматически заполняется из федерального реестра при указании ИНН'}
-
-
-
-
-
-
-
-
-
- {/* Дата регистрации */}
- {user?.organization?.registrationDate && (
-
-
-
-
-
-
- )}
-
- {/* Настройка рынка для поставщиков */}
- {user?.organization?.type === 'WHOLESALE' && (
-
-
-
- {isEditing ? (
-
- ) : (
-
- )}
-
- Физический рынок, где работает поставщик. Товары наследуют рынок от организации.
-
-
-
- )}
-
-
-
-
- {/* Финансовые данные */}
- {(user?.organization?.type === 'FULFILLMENT' ||
- user?.organization?.type === 'LOGIST' ||
- user?.organization?.type === 'WHOLESALE' ||
- user?.organization?.type === 'SELLER') && (
-
-
- {/* Заголовок вкладки с кнопками */}
-
-
-
-
-
Финансовые данные
-
Банковские реквизиты для расчетов
-
-
-
- {formData.bankName && formData.bik && formData.accountNumber && formData.corrAccount && (
-
-
- Заполнено
-
- )}
-
- {isEditing ? (
- <>
-
-
- >
- ) : (
-
- )}
-
-
-
-
-
-
- handleInputChange('bankName', e.target.value)}
- placeholder="ПАО Сбербанк"
- readOnly={!isEditing}
- className="glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70"
- />
-
-
-
-
-
-
- handleInputChange('accountNumber', e.target.value)}
- placeholder="40702810123456789012"
- readOnly={!isEditing}
- className="glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70"
- />
-
-
-
-
- )}
-
- {/* API ключи для селлера */}
- {user?.organization?.type === 'SELLER' && (
-
-
- {/* Заголовок вкладки с кнопками */}
-
-
-
-
-
API ключи маркетплейсов
-
Интеграция с торговыми площадками
-
-
-
- {user?.organization?.apiKeys?.length > 0 && (
-
-
- Настроено
-
- )}
-
- {isEditing ? (
- <>
-
-
- >
- ) : (
-
- )}
-
-
-
-
-
-
-
key.marketplace === 'WILDBERRIES')
- ? '••••••••••••••••••••'
- : ''
- }
- onChange={(e) => handleInputChange('wildberriesApiKey', e.target.value)}
- placeholder="Введите API ключ Wildberries"
- readOnly={!isEditing}
- className="glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70"
- autoComplete="off"
- spellCheck="false"
- />
- {(user?.organization?.apiKeys?.find((key) => key.marketplace === 'WILDBERRIES') ||
- (formData.wildberriesApiKey && isEditing)) && (
-
-
- {!isEditing ? 'API ключ настроен' : 'Будет сохранен'}
-
- )}
-
-
-
-
-
key.marketplace === 'OZON')
- ? '••••••••••••••••••••'
- : ''
- }
- onChange={(e) => handleInputChange('ozonApiKey', e.target.value)}
- placeholder="Введите API ключ Ozon"
- readOnly={!isEditing}
- className="glass-input text-white placeholder:text-white/40 h-10 read-only:opacity-70"
- autoComplete="off"
- spellCheck="false"
- />
- {(user?.organization?.apiKeys?.find((key) => key.marketplace === 'OZON') ||
- (formData.ozonApiKey && isEditing)) && (
-
-
- {!isEditing ? 'API ключ настроен' : 'Будет сохранен'}
-
- )}
-
-
-
-
- )}
-
- {/* Инструменты */}
-
-
- {/* Заголовок вкладки */}
-
-
-
-
-
Инструменты
-
Дополнительные возможности для бизнеса
-
-
-
-
-
-
-
-
Инструменты в разработке
-
- Здесь будут размещены полезные бизнес-инструменты: калькуляторы, аналитика, планировщики и
- автоматизация процессов.
-
-
-
- Скоро появится
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/components/dashboard/user-settings/hooks/useOrganizationSettings.ts b/src/components/dashboard/user-settings/hooks/useOrganizationSettings.ts
index a7779eb..4a2a759 100644
--- a/src/components/dashboard/user-settings/hooks/useOrganizationSettings.ts
+++ b/src/components/dashboard/user-settings/hooks/useOrganizationSettings.ts
@@ -1,14 +1,14 @@
import { useMutation } from '@apollo/client'
import { useState, useCallback } from 'react'
+import { useAuthContext } from '@/contexts/AuthContext'
import { UPDATE_USER_PROFILE, UPDATE_ORGANIZATION_BY_INN } from '@/graphql/mutations'
-import { useAuth } from '@/hooks/useAuth'
import { apolloClient } from '@/lib/apollo-client'
import type { UserSettingsFormData, SaveMessage } from '../types/user-settings.types'
export function useOrganizationSettings() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [,] = useMutation(UPDATE_USER_PROFILE)
const [updateOrganizationByInn, { loading: isUpdatingOrganization }] = useMutation(UPDATE_ORGANIZATION_BY_INN)
const [saveMessage, setSaveMessage] = useState(null)
diff --git a/src/components/dashboard/user-settings/hooks/useProfileSettings.ts b/src/components/dashboard/user-settings/hooks/useProfileSettings.ts
index aa05c22..f2e40cf 100644
--- a/src/components/dashboard/user-settings/hooks/useProfileSettings.ts
+++ b/src/components/dashboard/user-settings/hooks/useProfileSettings.ts
@@ -1,14 +1,14 @@
import { useMutation } from '@apollo/client'
import { useState, useCallback } from 'react'
+import { useAuthContext } from '@/contexts/AuthContext'
import { UPDATE_USER_PROFILE } from '@/graphql/mutations'
-import { useAuth } from '@/hooks/useAuth'
import S3Service from '@/services/s3-service'
import type { SaveMessage } from '../types/user-settings.types'
export function useProfileSettings() {
- const { user, updateUser } = useAuth()
+ const { user, updateUser } = useAuthContext()
const [updateUserProfile, { loading: isSaving }] = useMutation(UPDATE_USER_PROFILE)
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false)
const [localAvatarUrl, setLocalAvatarUrl] = useState(null)
diff --git a/src/components/economics/economics-page-wrapper.tsx b/src/components/economics/economics-page-wrapper.tsx
index 720b1c7..4e4179f 100644
--- a/src/components/economics/economics-page-wrapper.tsx
+++ b/src/components/economics/economics-page-wrapper.tsx
@@ -1,6 +1,6 @@
'use client'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { FulfillmentEconomicsPage } from './fulfillment-economics-page'
import { LogistEconomicsPage } from './logist-economics-page'
@@ -8,7 +8,7 @@ import { SellerEconomicsPage } from './seller-economics-page'
import { WholesaleEconomicsPage } from './wholesale-economics-page'
export function EconomicsPageWrapper() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
// Проверка доступа - только авторизованные пользователи с организацией
if (!user?.organization?.type) {
diff --git a/src/components/economics/fulfillment-economics-page.tsx b/src/components/economics/fulfillment-economics-page.tsx
index f9cc26e..7debac0 100644
--- a/src/components/economics/fulfillment-economics-page.tsx
+++ b/src/components/economics/fulfillment-economics-page.tsx
@@ -4,11 +4,11 @@ import { TrendingUp } from 'lucide-react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
export function FulfillmentEconomicsPage() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const getOrganizationName = () => {
diff --git a/src/components/economics/logist-economics-page.tsx b/src/components/economics/logist-economics-page.tsx
index 4b02b73..7febaef 100644
--- a/src/components/economics/logist-economics-page.tsx
+++ b/src/components/economics/logist-economics-page.tsx
@@ -4,11 +4,11 @@ import { Calculator } from 'lucide-react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
export function LogistEconomicsPage() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const getOrganizationName = () => {
diff --git a/src/components/economics/seller-economics-page.tsx b/src/components/economics/seller-economics-page.tsx
index 6872440..e84cbab 100644
--- a/src/components/economics/seller-economics-page.tsx
+++ b/src/components/economics/seller-economics-page.tsx
@@ -4,11 +4,11 @@ import { DollarSign } from 'lucide-react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
export function SellerEconomicsPage() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const getOrganizationName = () => {
diff --git a/src/components/economics/wholesale-economics-page.tsx b/src/components/economics/wholesale-economics-page.tsx
index 555464c..ca030a1 100644
--- a/src/components/economics/wholesale-economics-page.tsx
+++ b/src/components/economics/wholesale-economics-page.tsx
@@ -4,11 +4,11 @@ import { BarChart3 } from 'lucide-react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
export function WholesaleEconomicsPage() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const getOrganizationName = () => {
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/index.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/index.tsx
index 5f5cd5c..8e9c426 100644
--- a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/index.tsx
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/index.tsx
@@ -4,7 +4,7 @@
// 🔄 СИСТЕМА ПЕРЕКЛЮЧЕНИЯ ПО ТИПУ ОРГАНИЗАЦИИ - УНИВЕРСАЛЬНАЯ ПОСТАВКА
// =============================================================================
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { ModularVersion } from './modular-version' // Фулфилмент версия
import { MonolithicVersion } from './monolithic-version' // Фолбэк версия
@@ -14,7 +14,7 @@ import { SellerModularVersion } from './seller-modular-version' // 🆕 Сел
const USE_MODULAR_ARCHITECTURE = true // 👈 ПЕРЕКЛЮЧАТЕЛЬ: true = модульная, false = монолитная
export default function CreateConsumablesSupplyV2Page() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
// 🔄 Выбор версии по типу организации
if (USE_MODULAR_ARCHITECTURE && user?.organization) {
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/modular-version.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/modular-version.tsx
index fbd16ba..f7fa5ba 100644
--- a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/modular-version.tsx
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/modular-version.tsx
@@ -10,7 +10,7 @@ import { useRouter } from 'next/navigation'
import React from 'react'
import { Sidebar } from '@/components/dashboard/sidebar'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
// 📦 Импорт модульных компонентов
@@ -28,7 +28,7 @@ import {
export function ModularVersion() {
const router = useRouter()
const { getSidebarMargin } = useSidebar()
- const { user: _user } = useAuth()
+ const { user: _user } = useAuthContext()
// 📋 Управление состоянием формы
const {
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/monolithic-version.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/monolithic-version.tsx
index 8ded1b2..d604a72 100644
--- a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/monolithic-version.tsx
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/monolithic-version.tsx
@@ -18,7 +18,7 @@ import {
CREATE_FULFILLMENT_CONSUMABLE_SUPPLY,
GET_MY_FULFILLMENT_CONSUMABLE_SUPPLIES,
} from '@/graphql/queries/fulfillment-consumables-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
interface FulfillmentConsumableSupplier {
@@ -67,7 +67,7 @@ interface SelectedFulfillmentConsumable {
export function MonolithicVersion() {
const router = useRouter()
const { getSidebarMargin } = useSidebar()
- const { user: _user } = useAuth()
+ const { user: _user } = useAuthContext()
const [selectedSupplier, setSelectedSupplier] = useState(null)
const [selectedLogistics, setSelectedLogistics] = useState(null)
const [selectedConsumables, setSelectedConsumables] = useState([])
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/seller-modular-version.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/seller-modular-version.tsx
index 185bd71..ff18396 100644
--- a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/seller-modular-version.tsx
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2-modular/seller-modular-version.tsx
@@ -10,7 +10,7 @@ import { useRouter } from 'next/navigation'
import React from 'react'
import { Sidebar } from '@/components/dashboard/sidebar'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
// 📦 Импорт селлерских компонентов
@@ -30,7 +30,7 @@ import { useSellerSupplyForm, useSellerSupplyCreation, useSellerSupplierData } f
export function SellerModularVersion() {
const router = useRouter()
const { getSidebarMargin } = useSidebar()
- const { user: _user } = useAuth()
+ const { user: _user } = useAuthContext()
// 📋 Управление состоянием формы СЕЛЛЕРА
const {
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2.tsx
index 25975b5..9c95023 100644
--- a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2.tsx
+++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2.tsx
@@ -18,7 +18,7 @@ import {
CREATE_FULFILLMENT_CONSUMABLE_SUPPLY,
GET_MY_FULFILLMENT_CONSUMABLE_SUPPLIES,
} from '@/graphql/queries/fulfillment-consumables-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
interface FulfillmentConsumableSupplier {
@@ -67,7 +67,7 @@ interface SelectedFulfillmentConsumable {
export default function CreateFulfillmentConsumablesSupplyV2Page() {
const router = useRouter()
const { getSidebarMargin } = useSidebar()
- const { user: _user } = useAuth()
+ const { user: _user } = useAuthContext()
const [selectedSupplier, setSelectedSupplier] = useState(null)
const [selectedLogistics, setSelectedLogistics] = useState(null)
const [selectedConsumables, setSelectedConsumables] = useState([])
diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2.tsx.backup b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2.tsx.backup
deleted file mode 100644
index 67af69f..0000000
--- a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2.tsx.backup
+++ /dev/null
@@ -1,308 +0,0 @@
-'use client'
-
-import { useMutation, useQuery } from '@apollo/client'
-import { Calendar, Plus, Trash2 } from 'lucide-react'
-import { useRouter } from 'next/navigation'
-import React, { useState } from 'react'
-import { toast } from 'sonner'
-
-import { Button } from '@/components/ui/button'
-import { Card } from '@/components/ui/card'
-import { Input } from '@/components/ui/input'
-import { Label } from '@/components/ui/label'
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
-import { Textarea } from '@/components/ui/textarea'
-import { CREATE_FULFILLMENT_CONSUMABLE_SUPPLY } from '@/graphql/queries/fulfillment-consumables-v2'
-import { GET_MY_COUNTERPARTIES, GET_ORGANIZATION_PRODUCTS } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
-
-interface Product {
- id: string
- name: string
- article: string
- price: number
- quantity: number
- type: string
-}
-
-interface Organization {
- id: string
- name: string
- inn: string
- type: string
-}
-
-interface SupplyItem {
- productId: string
- requestedQuantity: number
- product?: Product
-}
-
-export default function CreateFulfillmentConsumablesSupplyV2Page() {
- const router = useRouter()
- const { user } = useAuth()
- const [selectedSupplierId, setSelectedSupplierId] = useState('')
- const [requestedDeliveryDate, setRequestedDeliveryDate] = useState('')
- const [notes, setNotes] = useState('')
- const [items, setItems] = useState([])
-
- // Получаем список контрагентов-поставщиков
- const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery<{
- myCounterparties: Organization[]
- }>(GET_MY_COUNTERPARTIES)
-
- // Получаем товары выбранного поставщика
- const { data: productsData, loading: productsLoading } = useQuery<{
- organizationProducts: Product[]
- }>(GET_ORGANIZATION_PRODUCTS, {
- variables: { organizationId: selectedSupplierId, type: 'CONSUMABLE' },
- skip: !selectedSupplierId,
- })
-
- const [createSupply, { loading: creating }] = useMutation(CREATE_FULFILLMENT_CONSUMABLE_SUPPLY, {
- onCompleted: (data) => {
- if (data.createFulfillmentConsumableSupply.success) {
- toast.success('Поставка успешно создана')
- router.push('/fulfillment-supplies')
- } else {
- toast.error(data.createFulfillmentConsumableSupply.message)
- }
- },
- onError: (error) => {
- toast.error(error.message)
- },
- })
-
- const suppliers = counterpartiesData?.myCounterparties.filter(
- (org) => org.type === 'WHOLESALE'
- ) || []
-
- const consumableProducts = productsData?.organizationProducts || []
-
- const addItem = () => {
- setItems([...items, { productId: '', requestedQuantity: 1 }])
- }
-
- const removeItem = (index: number) => {
- setItems(items.filter((_, i) => i !== index))
- }
-
- const updateItem = (index: number, field: keyof SupplyItem, value: string | number) => {
- const newItems = [...items]
- if (field === 'productId') {
- const product = consumableProducts.find(p => p.id === value)
- newItems[index] = { ...newItems[index], [field]: value, product }
- } else {
- newItems[index] = { ...newItems[index], [field]: value }
- }
- setItems(newItems)
- }
-
- const handleSubmit = () => {
- // Валидация
- if (!selectedSupplierId) {
- toast.error('Выберите поставщика')
- return
- }
-
- if (!requestedDeliveryDate) {
- toast.error('Укажите желаемую дату доставки')
- return
- }
-
- if (items.length === 0) {
- toast.error('Добавьте хотя бы один товар')
- return
- }
-
- const invalidItems = items.filter(item => !item.productId || item.requestedQuantity <= 0)
- if (invalidItems.length > 0) {
- toast.error('Заполните все товары корректно')
- return
- }
-
- // Создаем поставку
- createSupply({
- variables: {
- input: {
- supplierId: selectedSupplierId,
- requestedDeliveryDate,
- items: items.map(item => ({
- productId: item.productId,
- requestedQuantity: item.requestedQuantity,
- })),
- notes: notes || undefined,
- },
- },
- })
- }
-
- const totalAmount = items.reduce((sum, item) => {
- if (item.product) {
- return sum + (item.product.price * item.requestedQuantity)
- }
- return sum
- }, 0)
-
- return (
-
-
-
Создать поставку расходников ФФ (v2)
-
Новая система поставок
-
-
-
-
- {/* Выбор поставщика */}
-
-
-
-
-
- {/* Дата доставки */}
-
-
-
-
- setRequestedDeliveryDate(e.target.value)}
- className="pl-10"
- min={new Date().toISOString().split('T')[0]}
- />
-
-
-
- {/* Товары */}
-
-
-
-
-
-
- {items.length === 0 ? (
-
- {selectedSupplierId ? 'Нажмите "Добавить товар" для начала' : 'Сначала выберите поставщика'}
-
- ) : (
-
- {items.map((item, index) => (
-
-
-
-
-
-
-
-
- updateItem(index, 'requestedQuantity', parseInt(e.target.value) || 0)}
- />
-
-
-
-
- ))}
-
- )}
-
-
- {/* Заметки */}
-
-
-
-
- {/* Итого */}
- {items.length > 0 && (
-
-
- Итого:
- {totalAmount.toLocaleString('ru-RU')} ₽
-
-
- )}
-
- {/* Кнопки */}
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/components/fulfillment-supplies/fulfillment-goods-new/hooks/useFulfillmentGoodsData.ts b/src/components/fulfillment-supplies/fulfillment-goods-new/hooks/useFulfillmentGoodsData.ts
index a3bce0b..e6d028e 100644
--- a/src/components/fulfillment-supplies/fulfillment-goods-new/hooks/useFulfillmentGoodsData.ts
+++ b/src/components/fulfillment-supplies/fulfillment-goods-new/hooks/useFulfillmentGoodsData.ts
@@ -7,13 +7,13 @@ import { toast } from 'sonner'
import { GET_MY_SELLER_GOODS_SUPPLY_REQUESTS } from '@/graphql/mutations/seller-goods-v2'
import { GET_LOGISTICS_PARTNERS } from '@/graphql/queries'
import { GET_MY_EMPLOYEES_V2 } from '@/graphql/queries/employees-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useRealtime } from '@/hooks/useRealtime'
import type { FulfillmentGoodsSupply, Employee, LogisticsPartner } from '../types/fulfillment-goods.types'
export function useFulfillmentGoodsData() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
// Загружаем товарные поставки V2
const { data: goodsData, loading: goodsLoading, error: goodsError, refetch: refetchGoods } = useQuery(
diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx
index 5773587..d5fd4d1 100644
--- a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx
+++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-consumables-orders-tab.tsx
@@ -36,7 +36,7 @@ import {
import { GET_MY_EMPLOYEES_V2 } from '@/graphql/queries/employees-v2'
import { GET_MY_FULFILLMENT_CONSUMABLES_V2 } from '@/graphql/queries/fulfillment-services-v2'
import { GET_INCOMING_SELLER_SUPPLIES } from '@/graphql/queries/seller-consumables-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
interface SupplyOrder {
id: string
@@ -117,7 +117,7 @@ export function FulfillmentConsumablesOrdersTab() {
const [selectedEmployees, setSelectedEmployees] = useState<{
[orderId: string]: string
}>({})
- const { user } = useAuth()
+ const { user } = useAuthContext()
// Запросы данных
const { data: employeesData, loading: employeesLoading, error: employeesError } = useQuery(GET_MY_EMPLOYEES_V2)
diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-orders-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-orders-tab.tsx
index db32a30..e24955c 100644
--- a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-orders-tab.tsx
+++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-orders-tab.tsx
@@ -28,7 +28,7 @@ import {
GET_WAREHOUSE_PRODUCTS,
} from '@/graphql/queries'
import { GET_MY_EMPLOYEES_V2 } from '@/graphql/queries/employees-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
interface SupplyOrder {
id: string
@@ -101,7 +101,7 @@ interface SupplyOrder {
}
export function FulfillmentGoodsOrdersTab() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [expandedOrders, setExpandedOrders] = useState>(new Set())
const [selectedEmployee, setSelectedEmployee] = useState<{[orderId: string]: string}>({})
const [selectedLogistics, setSelectedLogistics] = useState<{[orderId: string]: string}>({})
diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/pvz-returns-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/pvz-returns-tab.tsx
index 3359329..ad0d055 100644
--- a/src/components/fulfillment-supplies/fulfillment-supplies/pvz-returns-tab.tsx
+++ b/src/components/fulfillment-supplies/fulfillment-supplies/pvz-returns-tab.tsx
@@ -19,7 +19,7 @@ import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { WildberriesService, type WBClaim, type WBClaimsResponse } from '@/services/wildberries-service'
// Интерфейс для обработанных данных возврата
@@ -41,7 +41,7 @@ interface ProcessedClaim {
}
export function PvzReturnsTab() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState('all')
const [archiveFilter, setArchiveFilter] = useState(false)
diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/seller-materials-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/seller-materials-tab.tsx
index 834a117..faf6f55 100644
--- a/src/components/fulfillment-supplies/fulfillment-supplies/seller-materials-tab.tsx
+++ b/src/components/fulfillment-supplies/fulfillment-supplies/seller-materials-tab.tsx
@@ -9,7 +9,7 @@ import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { GET_SUPPLY_ORDERS } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
// Интерфейс для заказа поставки от селлера
interface SellerSupplyOrder {
@@ -48,7 +48,7 @@ interface SellerSupplyOrder {
}
export function SellerMaterialsTab() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState('all')
diff --git a/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard.tsx.backup b/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard.tsx.backup
deleted file mode 100644
index 1715470..0000000
--- a/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard.tsx.backup
+++ /dev/null
@@ -1,2012 +0,0 @@
-'use client'
-
-import { useQuery } from '@apollo/client'
-import {
- Package,
- TrendingUp,
- TrendingDown,
- AlertTriangle,
- RotateCcw,
- Wrench,
- Users,
- Box,
- Search,
- ArrowUpDown,
- Store,
- Package2,
- Eye,
- EyeOff,
- ChevronRight,
- ChevronDown,
- Layers,
- Truck,
- Clock,
- CheckCircle,
- Settings,
-} from 'lucide-react'
-import { useRouter } from 'next/navigation'
-import { useState, useMemo } from 'react'
-import { toast } from 'sonner'
-
-import { Sidebar } from '@/components/dashboard/sidebar'
-import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar'
-import { Badge } from '@/components/ui/badge'
-import { Button } from '@/components/ui/button'
-import { Card } from '@/components/ui/card'
-import { Input } from '@/components/ui/input'
-import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
-import {
- GET_MY_COUNTERPARTIES,
- GET_SUPPLY_ORDERS,
- GET_WAREHOUSE_PRODUCTS,
- GET_MY_SUPPLIES, // Расходники селлеров (старые данные заказов)
- GET_SELLER_SUPPLIES_ON_WAREHOUSE, // Расходники селлеров на складе (новый API)
- GET_MY_FULFILLMENT_SUPPLIES, // Расходники фулфилмента
- GET_FULFILLMENT_WAREHOUSE_STATS, // Статистика склада с изменениями за сутки
-} from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
-import { useSidebar } from '@/hooks/useSidebar'
-import { useRealtime } from '@/hooks/useRealtime'
-
-import { WbReturnClaims } from './wb-return-claims'
-
-// Типы данных
-interface ProductVariant {
- id: string
- name: string // Размер, характеристика, вариант упаковки
- // Места и количества для каждого типа на уровне варианта
- productPlace?: string
- productQuantity: number
- goodsPlace?: string
- goodsQuantity: number
- defectsPlace?: string
- defectsQuantity: number
- sellerSuppliesPlace?: string
- sellerSuppliesQuantity: number
- sellerSuppliesOwners?: string[] // Владельцы расходников
- pvzReturnsPlace?: string
- pvzReturnsQuantity: number
-}
-
-interface ProductItem {
- id: string
- name: string
- article: string
- // Места и количества для каждого типа
- productPlace?: string
- productQuantity: number
- goodsPlace?: string
- goodsQuantity: number
- defectsPlace?: string
- defectsQuantity: number
- sellerSuppliesPlace?: string
- sellerSuppliesQuantity: number
- sellerSuppliesOwners?: string[] // Владельцы расходников
- pvzReturnsPlace?: string
- pvzReturnsQuantity: number
- // Третий уровень - варианты товара
- variants?: ProductVariant[]
-}
-
-interface StoreData {
- id: string
- name: string
- logo?: string
- avatar?: string // Аватар пользователя организации
- products: number
- goods: number
- defects: number
- sellerSupplies: number
- pvzReturns: number
- // Изменения за сутки
- productsChange: number
- goodsChange: number
- defectsChange: number
- sellerSuppliesChange: number
- pvzReturnsChange: number
- // Детализация по товарам
- items: ProductItem[]
-}
-
-interface WarehouseStats {
- products: { current: number; change: number }
- goods: { current: number; change: number }
- defects: { current: number; change: number }
- pvzReturns: { current: number; change: number }
- fulfillmentSupplies: { current: number; change: number }
- sellerSupplies: { current: number; change: number }
-}
-
-interface Supply {
- id: string
- name: string
- description?: string
- price: number
- quantity: number
- unit: string
- category: string
- status: string
- date: string
- supplier: string
- minStock: number
- currentStock: number
-}
-
-interface SupplyOrder {
- id: string
- status: 'PENDING' | 'CONFIRMED' | 'IN_TRANSIT' | 'DELIVERED' | 'CANCELLED'
- deliveryDate: string
- totalAmount: number
- totalItems: number
- partner: {
- id: string
- name: string
- fullName: string
- }
- items: Array<{
- id: string
- quantity: number
- product: {
- id: string
- name: string
- article: string
- }
- }>
-}
-
-/**
- * Цветовая схема уровней:
- * 🔵 Уровень 1: Магазины - УНИКАЛЬНЫЕ ЦВЕТА для каждого магазина:
- * - ТехноМир: Синий (blue-400/500) - технологии
- * - Стиль и Комфорт: Розовый (pink-400/500) - мода/одежда
- * - Зелёный Дом: Изумрудный (emerald-400/500) - природа/сад
- * - Усиленная видимость: жирная левая граница (8px), тень, светлый текст
- * 🟢 Уровень 2: Товары - Зеленый (green-500)
- * 🟠 Уровень 3: Варианты товаров - Оранжевый (orange-500)
- *
- * Каждый уровень имеет:
- * - Цветной индикатор (круглая точка увеличивающегося размера)
- * - Цветную левую границу с увеличивающимся отступом и толщиной
- * - Соответствующий цвет фона и границ
- * - Скроллбары в цвете уровня
- * - Контрастный цвет текста для лучшей читаемости
- */
-export function FulfillmentWarehouseDashboard() {
- const router = useRouter()
- const { getSidebarMargin } = useSidebar()
- const { user } = useAuth()
-
- // Состояния для поиска и фильтрации
- const [searchTerm, setSearchTerm] = useState('')
- const [sortField, setSortField] = useState('name')
- const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc')
- const [expandedStores, setExpandedStores] = useState>(new Set())
- const [expandedItems, setExpandedItems] = useState>(new Set())
- const [showReturnClaims, setShowReturnClaims] = useState(false)
- const [showAdditionalValues, setShowAdditionalValues] = useState(true)
-
- // Загружаем данные из GraphQL
- const {
- data: counterpartiesData,
- loading: counterpartiesLoading,
- error: counterpartiesError,
- refetch: refetchCounterparties,
- } = useQuery(GET_MY_COUNTERPARTIES, {
- fetchPolicy: 'cache-and-network', // Всегда проверяем актуальные данные
- })
- const {
- data: ordersData,
- loading: ordersLoading,
- error: ordersError,
- refetch: refetchOrders,
- } = useQuery(GET_SUPPLY_ORDERS, {
- fetchPolicy: 'cache-and-network',
- })
- const {
- data: productsData,
- loading: productsLoading,
- error: productsError,
- refetch: refetchProducts,
- } = useQuery(GET_WAREHOUSE_PRODUCTS, {
- fetchPolicy: 'cache-and-network',
- })
-
- // Загружаем расходники селлеров на складе фулфилмента
- const {
- data: sellerSuppliesData,
- loading: sellerSuppliesLoading,
- error: sellerSuppliesError,
- refetch: refetchSellerSupplies,
- } = useQuery(GET_SELLER_SUPPLIES_ON_WAREHOUSE, {
- fetchPolicy: 'cache-and-network',
- })
-
- // Загружаем расходники фулфилмента
- const {
- data: fulfillmentSuppliesData,
- loading: fulfillmentSuppliesLoading,
- error: fulfillmentSuppliesError,
- refetch: refetchFulfillmentSupplies,
- } = useQuery(GET_MY_FULFILLMENT_SUPPLIES, {
- fetchPolicy: 'cache-and-network',
- })
-
- // Загружаем статистику склада с изменениями за сутки
- const {
- data: warehouseStatsData,
- loading: warehouseStatsLoading,
- error: warehouseStatsError,
- refetch: refetchWarehouseStats,
- } = useQuery(GET_FULFILLMENT_WAREHOUSE_STATS, {
- fetchPolicy: 'no-cache', // Принудительно обходим кеш
- })
-
- // Real-time: обновляем ключевые блоки при событиях поставок/склада
- useRealtime({
- onEvent: (evt) => {
- switch (evt.type) {
- case 'supply-order:new':
- case 'supply-order:updated':
- refetchOrders()
- refetchWarehouseStats()
- refetchProducts()
- refetchSellerSupplies()
- refetchFulfillmentSupplies()
- break
- case 'warehouse:changed':
- refetchWarehouseStats()
- refetchFulfillmentSupplies()
- break
- }
- },
- })
-
- // Логируем статистику склада для отладки
- console.warn('📊 WAREHOUSE STATS DEBUG:', {
- loading: warehouseStatsLoading,
- error: warehouseStatsError?.message,
- data: warehouseStatsData,
- hasData: !!warehouseStatsData?.fulfillmentWarehouseStats,
- })
-
- // Детальное логирование данных статистики
- if (warehouseStatsData?.fulfillmentWarehouseStats) {
- console.warn('📈 DETAILED WAREHOUSE STATS:', {
- products: warehouseStatsData.fulfillmentWarehouseStats.products,
- goods: warehouseStatsData.fulfillmentWarehouseStats.goods,
- defects: warehouseStatsData.fulfillmentWarehouseStats.defects,
- pvzReturns: warehouseStatsData.fulfillmentWarehouseStats.pvzReturns,
- fulfillmentSupplies: warehouseStatsData.fulfillmentWarehouseStats.fulfillmentSupplies,
- sellerSupplies: warehouseStatsData.fulfillmentWarehouseStats.sellerSupplies,
- })
- }
-
- // Получаем данные магазинов, заказов и товаров
- const allCounterparties = counterpartiesData?.myCounterparties || []
- const sellerPartners = allCounterparties.filter((partner: { type: string }) => partner.type === 'SELLER')
- const supplyOrders: SupplyOrder[] = ordersData?.supplyOrders || []
- const allProducts = productsData?.warehouseProducts || []
- const sellerSupplies = sellerSuppliesData?.sellerSuppliesOnWarehouse || [] // Расходники селлеров на складе
- const myFulfillmentSupplies = fulfillmentSuppliesData?.myFulfillmentSupplies || [] // Расходники фулфилмента
-
- // Логирование для отладки
- console.warn('🏪 Данные склада фулфилмента:', {
- allCounterpartiesCount: allCounterparties.length,
- sellerPartnersCount: sellerPartners.length,
- sellerPartners: sellerPartners.map((p: any) => ({
- id: p.id,
- name: p.name,
- fullName: p.fullName,
- type: p.type,
- })),
- ordersCount: supplyOrders.length,
- deliveredOrders: supplyOrders.filter((o) => o.status === 'DELIVERED').length,
- productsCount: allProducts.length,
- suppliesCount: sellerSupplies.length, // Добавляем логирование расходников
- supplies: sellerSupplies.map((s: any) => ({
- id: s.id,
- name: s.name,
- currentStock: s.currentStock,
- category: s.category,
- supplier: s.supplier,
- })),
- products: allProducts.map((p: any) => ({
- id: p.id,
- name: p.name,
- article: p.article,
- organizationName: p.organization?.name || p.organization?.fullName,
- organizationType: p.organization?.type,
- })),
- // Добавляем анализ соответствия товаров и расходников
- productSupplyMatching: allProducts.map((product: any) => {
- const matchingSupply = sellerSupplies.find((supply: any) => {
- return (
- supply.name.toLowerCase() === product.name.toLowerCase() ||
- supply.name.toLowerCase().includes(product.name.toLowerCase().split(' ')[0])
- )
- })
- return {
- productName: product.name,
- matchingSupplyName: matchingSupply?.name,
- matchingSupplyStock: matchingSupply?.currentStock,
- hasMatch: !!matchingSupply,
- }
- }),
- counterpartiesLoading,
- ordersLoading,
- productsLoading,
- sellerSuppliesLoading, // Добавляем статус загрузки расходников селлеров
- counterpartiesError: counterpartiesError?.message,
- ordersError: ordersError?.message,
- productsError: productsError?.message,
- sellerSuppliesError: sellerSuppliesError?.message, // Добавляем ошибки загрузки расходников селлеров
- })
-
- // Расчет поступлений расходников за сутки (выносим отдельно для использования в storeData)
- const suppliesReceivedToday = useMemo(() => {
- const deliveredOrders = supplyOrders.filter((o) => o.status === 'DELIVERED')
-
- // Подсчитываем расходники селлера из доставленных заказов за последние сутки
- const oneDayAgo = new Date()
- oneDayAgo.setDate(oneDayAgo.getDate() - 1)
-
- const recentDeliveredOrders = deliveredOrders.filter((order) => {
- const deliveryDate = new Date(order.deliveryDate)
- return deliveryDate >= oneDayAgo && order.fulfillmentCenter?.id // За последние сутки
- })
-
- const realSuppliesReceived = recentDeliveredOrders.reduce((sum, order) => sum + order.totalItems, 0)
-
- // Логирование для отладки
- console.warn('📦 Анализ поставок расходников за сутки:', {
- totalDeliveredOrders: deliveredOrders.length,
- recentDeliveredOrders: recentDeliveredOrders.length,
- recentOrders: recentDeliveredOrders.map((order) => ({
- id: order.id,
- deliveryDate: order.deliveryDate,
- totalItems: order.totalItems,
- status: order.status,
- })),
- realSuppliesReceived,
- oneDayAgo: oneDayAgo.toISOString(),
- })
-
- // Возвращаем реальное значение без fallback
- return realSuppliesReceived
- }, [supplyOrders])
-
- // Расчет использованных расходников за сутки (пока всегда 0, так как нет данных об использовании)
- const suppliesUsedToday = useMemo(() => {
- // TODO: Здесь должна быть логика подсчета использованных расходников
- // Пока возвращаем 0, так как нет данных об использовании
- return 0
- }, [])
-
- // Расчет изменений товаров за сутки (реальные данные)
- const productsReceivedToday = useMemo(() => {
- // Товары, поступившие за сутки из доставленных заказов
- const deliveredOrders = supplyOrders.filter((o) => o.status === 'DELIVERED')
- const oneDayAgo = new Date()
- oneDayAgo.setDate(oneDayAgo.getDate() - 1)
-
- const recentDeliveredOrders = deliveredOrders.filter((order) => {
- const deliveryDate = new Date(order.deliveryDate)
- return deliveryDate >= oneDayAgo && order.fulfillmentCenter?.id
- })
-
- const realProductsReceived = recentDeliveredOrders.reduce((sum, order) => sum + (order.totalItems || 0), 0)
-
- // Логирование для отладки
- console.warn('📦 Анализ поставок товаров за сутки:', {
- totalDeliveredOrders: deliveredOrders.length,
- recentDeliveredOrders: recentDeliveredOrders.length,
- recentOrders: recentDeliveredOrders.map((order) => ({
- id: order.id,
- deliveryDate: order.deliveryDate,
- totalItems: order.totalItems,
- status: order.status,
- })),
- realProductsReceived,
- oneDayAgo: oneDayAgo.toISOString(),
- })
-
- return realProductsReceived
- }, [supplyOrders])
-
- const productsUsedToday = useMemo(() => {
- // Товары, отправленные/использованные за сутки (пока 0, нет данных)
- return 0
- }, [])
-
- // Логирование статистики расходников для отладки
- console.warn('📊 Статистика расходников селлера:', {
- suppliesReceivedToday,
- suppliesUsedToday,
- totalSellerSupplies: sellerSupplies.reduce((sum: number, supply: any) => sum + (supply.currentStock || 0), 0),
- netChange: suppliesReceivedToday - suppliesUsedToday,
- })
-
- // Получаем статистику склада из GraphQL (с реальными изменениями за сутки)
- const warehouseStats: WarehouseStats = useMemo(() => {
- // Если данные еще загружаются, возвращаем нули
- if (warehouseStatsLoading || !warehouseStatsData?.fulfillmentWarehouseStats) {
- return {
- products: { current: 0, change: 0 },
- goods: { current: 0, change: 0 },
- defects: { current: 0, change: 0 },
- pvzReturns: { current: 0, change: 0 },
- fulfillmentSupplies: { current: 0, change: 0 },
- sellerSupplies: { current: 0, change: 0 },
- }
- }
-
- // Используем данные из GraphQL резолвера
- const stats = warehouseStatsData.fulfillmentWarehouseStats
-
- return {
- products: {
- current: stats.products.current,
- change: stats.products.change,
- },
- goods: {
- current: stats.goods.current,
- change: stats.goods.change,
- },
- defects: {
- current: stats.defects.current,
- change: stats.defects.change,
- },
- pvzReturns: {
- current: stats.pvzReturns.current,
- change: stats.pvzReturns.change,
- },
- fulfillmentSupplies: {
- current: stats.fulfillmentSupplies.current,
- change: stats.fulfillmentSupplies.change,
- },
- sellerSupplies: {
- current: stats.sellerSupplies.current,
- change: stats.sellerSupplies.change,
- },
- }
- }, [warehouseStatsData, warehouseStatsLoading])
-
- // Создаем структурированные данные склада на основе уникальных товаров
- const storeData: StoreData[] = useMemo(() => {
- if (!sellerPartners.length && !allProducts.length) return []
-
- // Группируем товары по названию, суммируя количества из разных поставок
- const groupedProducts = new Map<
- string,
- {
- name: string
- totalQuantity: number
- suppliers: string[]
- categories: string[]
- prices: number[]
- articles: string[]
- originalProducts: any[]
- }
- >()
-
- // Группируем товары из allProducts
- allProducts.forEach((product: any) => {
- const productName = product.name
- const quantity = product.orderedQuantity || 0
-
- if (groupedProducts.has(productName)) {
- const existing = groupedProducts.get(productName)!
- existing.totalQuantity += quantity
- existing.suppliers.push(product.organization?.name || product.organization?.fullName || 'Неизвестно')
- existing.categories.push(product.category?.name || 'Без категории')
- existing.prices.push(product.price || 0)
- existing.articles.push(product.article || '')
- existing.originalProducts.push(product)
- } else {
- groupedProducts.set(productName, {
- name: productName,
- totalQuantity: quantity,
- suppliers: [product.organization?.name || product.organization?.fullName || 'Неизвестно'],
- categories: [product.category?.name || 'Без категории'],
- prices: [product.price || 0],
- articles: [product.article || ''],
- originalProducts: [product],
- })
- }
- })
-
- // ИСПРАВЛЕНО: Группируем расходники по СЕЛЛЕРУ-ВЛАДЕЛЬЦУ, а не по названию
- const suppliesByOwner = new Map>()
-
- sellerSupplies.forEach((supply: any) => {
- const ownerId = supply.sellerOwner?.id
- const ownerName = supply.sellerOwner?.name || supply.sellerOwner?.fullName || 'Неизвестный селлер'
- const supplyName = supply.name
- const currentStock = supply.currentStock || 0
- const supplyType = supply.type
-
- // ИСПРАВЛЕНО: Строгая проверка согласно правилам
- if (!ownerId || supplyType !== 'SELLER_CONSUMABLES') {
- console.warn('⚠️ ОТФИЛЬТРОВАН расходник в компоненте (нарушение правил):', {
- id: supply.id,
- name: supplyName,
- type: supplyType,
- ownerId,
- ownerName,
- reason: !ownerId ? 'нет sellerOwner.id' : 'тип не SELLER_CONSUMABLES',
- })
- return // Пропускаем согласно ПРАВИЛУ 6 из секции 11.6
- }
-
- // Инициализируем группу для селлера, если её нет
- if (!suppliesByOwner.has(ownerId)) {
- suppliesByOwner.set(ownerId, new Map())
- }
-
- const ownerSupplies = suppliesByOwner.get(ownerId)!
-
- if (ownerSupplies.has(supplyName)) {
- // Суммируем количество, если расходник уже есть у этого селлера
- const existing = ownerSupplies.get(supplyName)!
- existing.quantity += currentStock
- } else {
- // Добавляем новый расходник для этого селлера
- ownerSupplies.set(supplyName, {
- quantity: currentStock,
- ownerName: ownerName,
- })
- }
- })
-
- // Логирование группировки
- console.warn('📊 Группировка товаров и расходников:', {
- groupedProductsCount: groupedProducts.size,
- suppliesByOwnerCount: suppliesByOwner.size,
- groupedProducts: Array.from(groupedProducts.entries()).map(([name, data]) => ({
- name,
- totalQuantity: data.totalQuantity,
- suppliersCount: data.suppliers.length,
- uniqueSuppliers: [...new Set(data.suppliers)],
- })),
- suppliesByOwner: Array.from(suppliesByOwner.entries()).map(([ownerId, ownerSupplies]) => ({
- ownerId,
- suppliesCount: ownerSupplies.size,
- totalQuantity: Array.from(ownerSupplies.values()).reduce((sum, s) => sum + s.quantity, 0),
- ownerName: Array.from(ownerSupplies.values())[0]?.ownerName || 'Unknown',
- supplies: Array.from(ownerSupplies.entries()).map(([name, data]) => ({
- name,
- quantity: data.quantity,
- })),
- })),
- })
-
- // Создаем виртуальных "партнеров" на основе уникальных товаров
- const uniqueProductNames = Array.from(groupedProducts.keys())
- const virtualPartners = Math.max(1, Math.min(sellerPartners.length, Math.ceil(uniqueProductNames.length / 8)))
-
- return Array.from({ length: virtualPartners }, (_, index) => {
- const startIndex = index * 8
- const endIndex = Math.min(startIndex + 8, uniqueProductNames.length)
- const partnerProductNames = uniqueProductNames.slice(startIndex, endIndex)
-
- const items: ProductItem[] = partnerProductNames.map((productName, itemIndex) => {
- const productData = groupedProducts.get(productName)!
- const itemProducts = productData.totalQuantity
-
- // ИСПРАВЛЕНО: Ищем расходники конкретного селлера-владельца
- let itemSuppliesQuantity = 0
- let suppliesOwners: string[] = []
-
- // Получаем реального селлера для этого виртуального партнера
- const realSeller = sellerPartners[index]
-
- if (realSeller?.id && suppliesByOwner.has(realSeller.id)) {
- const sellerSupplies = suppliesByOwner.get(realSeller.id)!
-
- // Ищем расходники этого селлера по названию товара
- const matchingSupply = sellerSupplies.get(productName)
-
- if (matchingSupply) {
- itemSuppliesQuantity = matchingSupply.quantity
- suppliesOwners = [matchingSupply.ownerName]
- } else {
- // Если нет точного совпадения, ищем частичное среди расходников ЭТОГО селлера
- for (const [supplyName, supplyData] of sellerSupplies.entries()) {
- if (
- supplyName.toLowerCase().includes(productName.toLowerCase()) ||
- productName.toLowerCase().includes(supplyName.toLowerCase())
- ) {
- itemSuppliesQuantity = supplyData.quantity
- suppliesOwners = [supplyData.ownerName]
- break
- }
- }
- }
- }
-
- // Если у этого селлера нет расходников для данного товара - оставляем 0
- // НЕ используем fallback, так как должны показывать только реальные данные
-
- console.warn(`📦 Товар "${productName}" (партнер: ${realSeller?.name || 'Unknown'}):`, {
- totalQuantity: itemProducts,
- suppliersCount: productData.suppliers.length,
- uniqueSuppliers: [...new Set(productData.suppliers)],
- sellerSuppliesQuantity: itemSuppliesQuantity,
- suppliesOwners: suppliesOwners,
- sellerId: realSeller?.id,
- hasSellerSupplies: itemSuppliesQuantity > 0,
- })
-
- return {
- id: `grouped-${productName}-${itemIndex}`, // Уникальный ID для группированного товара
- name: productName,
- article:
- productData.articles[0] ||
- `ART${(index + 1).toString().padStart(2, '0')}${(itemIndex + 1).toString().padStart(2, '0')}`,
- productPlace: `A${index + 1}-${itemIndex + 1}`,
- productQuantity: itemProducts, // Суммированное количество (реальные данные)
- goodsPlace: `B${index + 1}-${itemIndex + 1}`,
- goodsQuantity: 0, // Нет реальных данных о готовых товарах
- defectsPlace: `C${index + 1}-${itemIndex + 1}`,
- defectsQuantity: 0, // Нет реальных данных о браке
- sellerSuppliesPlace: `D${index + 1}-${itemIndex + 1}`,
- sellerSuppliesQuantity: itemSuppliesQuantity, // Суммированное количество расходников (реальные данные)
- sellerSuppliesOwners: suppliesOwners, // Владельцы расходников (ИСПРАВЛЕНО)
- pvzReturnsPlace: `E${index + 1}-${itemIndex + 1}`,
- pvzReturnsQuantity: 0, // Нет реальных данных о возвратах с ПВЗ
- // Создаем варианты товара
- variants:
- Math.random() > 0.5
- ? [
- {
- id: `grouped-${productName}-${itemIndex}-1`,
- name: 'Размер S',
- productPlace: `A${index + 1}-${itemIndex + 1}-1`,
- productQuantity: Math.floor(itemProducts * 0.4), // Часть от общего количества
- goodsPlace: `B${index + 1}-${itemIndex + 1}-1`,
- goodsQuantity: 0, // Нет реальных данных о готовых товарах
- defectsPlace: `C${index + 1}-${itemIndex + 1}-1`,
- defectsQuantity: 0, // Нет реальных данных о браке
- sellerSuppliesPlace: `D${index + 1}-${itemIndex + 1}-1`,
- sellerSuppliesQuantity: Math.floor(itemSuppliesQuantity * 0.4), // Часть от расходников
- sellerSuppliesOwners: suppliesOwners, // Владельцы расходников (ИСПРАВЛЕНО)
- pvzReturnsPlace: `E${index + 1}-${itemIndex + 1}-1`,
- pvzReturnsQuantity: 0, // Нет реальных данных о возвратах
- },
- {
- id: `grouped-${productName}-${itemIndex}-2`,
- name: 'Размер M',
- productPlace: `A${index + 1}-${itemIndex + 1}-2`,
- productQuantity: Math.floor(itemProducts * 0.4), // Часть от общего количества
- goodsPlace: `B${index + 1}-${itemIndex + 1}-2`,
- goodsQuantity: 0, // Нет реальных данных о готовых товарах
- defectsPlace: `C${index + 1}-${itemIndex + 1}-2`,
- defectsQuantity: 0, // Нет реальных данных о браке
- sellerSuppliesPlace: `D${index + 1}-${itemIndex + 1}-2`,
- sellerSuppliesQuantity: Math.floor(itemSuppliesQuantity * 0.4), // Часть от расходников
- sellerSuppliesOwners: suppliesOwners, // Владельцы расходников (ИСПРАВЛЕНО)
- pvzReturnsPlace: `E${index + 1}-${itemIndex + 1}-2`,
- pvzReturnsQuantity: 0, // Нет реальных данных о возвратах
- },
- {
- id: `grouped-${productName}-${itemIndex}-3`,
- name: 'Размер L',
- productPlace: `A${index + 1}-${itemIndex + 1}-3`,
- productQuantity: Math.floor(itemProducts * 0.2), // Оставшаяся часть
- goodsPlace: `B${index + 1}-${itemIndex + 1}-3`,
- goodsQuantity: 0, // Нет реальных данных о готовых товарах
- defectsPlace: `C${index + 1}-${itemIndex + 1}-3`,
- defectsQuantity: 0, // Нет реальных данных о браке
- sellerSuppliesPlace: `D${index + 1}-${itemIndex + 1}-3`,
- sellerSuppliesQuantity: Math.floor(itemSuppliesQuantity * 0.2), // Оставшаяся часть расходников
- sellerSuppliesOwners: suppliesOwners, // Владельцы расходников (ИСПРАВЛЕНО)
- pvzReturnsPlace: `E${index + 1}-${itemIndex + 1}-3`,
- pvzReturnsQuantity: 0, // Нет реальных данных о возвратах
- },
- ]
- : [],
- }
- })
-
- // Подсчитываем реальные суммы на основе товаров партнера
- const totalProducts = items.reduce((sum, item) => sum + item.productQuantity, 0)
- const totalGoods = items.reduce((sum, item) => sum + item.goodsQuantity, 0)
- const totalDefects = items.reduce((sum, item) => sum + item.defectsQuantity, 0)
-
- // Используем реальные данные из товаров для расходников селлера
- const totalSellerSupplies = items.reduce((sum, item) => sum + item.sellerSuppliesQuantity, 0)
- const totalPvzReturns = items.reduce((sum, item) => sum + item.pvzReturnsQuantity, 0)
-
- // Логирование общих сумм виртуального партнера
- const partnerName = sellerPartners[index]
- ? sellerPartners[index].name || sellerPartners[index].fullName || `Селлер ${index + 1}`
- : `Склад ${index + 1}`
-
- console.warn(`🏪 Партнер "${partnerName}":`, {
- totalProducts,
- totalGoods,
- totalDefects,
- totalSellerSupplies,
- totalPvzReturns,
- itemsCount: items.length,
- itemsWithSupplies: items.filter((item) => item.sellerSuppliesQuantity > 0).length,
- productNames: items.map((item) => item.name),
- hasRealPartner: !!sellerPartners[index],
- })
-
- // Рассчитываем изменения расходников для этого партнера
- // Распределяем общие поступления пропорционально количеству расходников партнера
- const totalVirtualPartners = Math.max(
- 1,
- Math.min(sellerPartners.length, Math.ceil(uniqueProductNames.length / 8)),
- )
-
- // Нет данных об изменениях продуктов для этого партнера
- const partnerProductsChange = 0
-
- // Реальные изменения расходников селлера для этого партнера
- const partnerSuppliesChange =
- totalSellerSupplies > 0
- ? Math.floor(
- (totalSellerSupplies /
- (sellerSupplies.reduce((sum: number, supply: any) => sum + (supply.currentStock || 0), 0) || 1)) *
- (suppliesReceivedToday - suppliesUsedToday),
- )
- : Math.floor((suppliesReceivedToday - suppliesUsedToday) / totalVirtualPartners)
-
- return {
- id: `virtual-partner-${index + 1}`,
- name: sellerPartners[index]
- ? sellerPartners[index].name || sellerPartners[index].fullName || `Селлер ${index + 1}`
- : `Склад ${index + 1}`, // Только если нет реального партнера
- avatar:
- sellerPartners[index]?.users?.[0]?.avatar ||
- `https://images.unsplash.com/photo-15312974840${index + 1}?w=100&h=100&fit=crop&crop=face`,
- products: totalProducts, // Реальная сумма товаров
- goods: totalGoods, // Реальная сумма готовых к отправке
- defects: totalDefects, // Реальная сумма брака
- sellerSupplies: totalSellerSupplies, // Реальная сумма расходников селлера
- pvzReturns: totalPvzReturns, // Реальная сумма возвратов
- productsChange: partnerProductsChange, // Реальные изменения товаров
- goodsChange: 0, // Нет реальных данных о готовых товарах
- defectsChange: 0, // Нет реальных данных о браке
- sellerSuppliesChange: partnerSuppliesChange, // Реальные изменения расходников
- pvzReturnsChange: 0, // Нет реальных данных о возвратах
- items,
- }
- })
- }, [sellerPartners, allProducts, sellerSupplies, suppliesReceivedToday])
-
- // Функции для аватаров магазинов
- const getInitials = (name: string): string => {
- return name
- .split(' ')
- .map((word) => word.charAt(0))
- .join('')
- .toUpperCase()
- .slice(0, 2)
- }
-
- const getColorForStore = (storeId: string): string => {
- const colors = [
- 'bg-blue-500',
- 'bg-green-500',
- 'bg-purple-500',
- 'bg-orange-500',
- 'bg-pink-500',
- 'bg-indigo-500',
- 'bg-teal-500',
- 'bg-red-500',
- 'bg-yellow-500',
- 'bg-cyan-500',
- ]
- const hash = storeId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0)
- return colors[hash % colors.length]
- }
-
- // Уникальные цветовые схемы для каждого магазина
- const getColorScheme = (storeId: string) => {
- const colorSchemes = {
- '1': {
- // Первый поставщик - Синий
- bg: 'bg-blue-500/5',
- border: 'border-blue-500/30',
- borderLeft: 'border-l-blue-400',
- text: 'text-blue-100',
- indicator: 'bg-blue-400 border-blue-300',
- hover: 'hover:bg-blue-500/10',
- header: 'bg-blue-500/20 border-blue-500/40',
- },
- '2': {
- // Второй поставщик - Розовый
- bg: 'bg-pink-500/5',
- border: 'border-pink-500/30',
- borderLeft: 'border-l-pink-400',
- text: 'text-pink-100',
- indicator: 'bg-pink-400 border-pink-300',
- hover: 'hover:bg-pink-500/10',
- header: 'bg-pink-500/20 border-pink-500/40',
- },
- '3': {
- // Третий поставщик - Зеленый
- bg: 'bg-emerald-500/5',
- border: 'border-emerald-500/30',
- borderLeft: 'border-l-emerald-400',
- text: 'text-emerald-100',
- indicator: 'bg-emerald-400 border-emerald-300',
- hover: 'hover:bg-emerald-500/10',
- header: 'bg-emerald-500/20 border-emerald-500/40',
- },
- '4': {
- // Четвертый поставщик - Фиолетовый
- bg: 'bg-purple-500/5',
- border: 'border-purple-500/30',
- borderLeft: 'border-l-purple-400',
- text: 'text-purple-100',
- indicator: 'bg-purple-400 border-purple-300',
- hover: 'hover:bg-purple-500/10',
- header: 'bg-purple-500/20 border-purple-500/40',
- },
- '5': {
- // Пятый поставщик - Оранжевый
- bg: 'bg-orange-500/5',
- border: 'border-orange-500/30',
- borderLeft: 'border-l-orange-400',
- text: 'text-orange-100',
- indicator: 'bg-orange-400 border-orange-300',
- hover: 'hover:bg-orange-500/10',
- header: 'bg-orange-500/20 border-orange-500/40',
- },
- '6': {
- // Шестой поставщик - Индиго
- bg: 'bg-indigo-500/5',
- border: 'border-indigo-500/30',
- borderLeft: 'border-l-indigo-400',
- text: 'text-indigo-100',
- indicator: 'bg-indigo-400 border-indigo-300',
- hover: 'hover:bg-indigo-500/10',
- header: 'bg-indigo-500/20 border-indigo-500/40',
- },
- }
-
- // Если у нас больше поставщиков чем цветовых схем, используем циклический выбор
- const schemeKeys = Object.keys(colorSchemes)
- const schemeIndex = (parseInt(storeId) - 1) % schemeKeys.length
- const selectedKey = schemeKeys[schemeIndex] || '1'
-
- return colorSchemes[selectedKey as keyof typeof colorSchemes] || colorSchemes['1']
- }
-
- // Фильтрация и сортировка данных
- const filteredAndSortedStores = useMemo(() => {
- console.warn('🔍 Фильтрация поставщиков:', {
- storeDataLength: storeData.length,
- searchTerm,
- sortField,
- sortOrder,
- })
-
- const filtered = storeData.filter((store) => store.name.toLowerCase().includes(searchTerm.toLowerCase()))
-
- console.warn('📋 Отфильтрованные поставщики:', {
- filteredLength: filtered.length,
- storeNames: filtered.map((s) => s.name),
- })
-
- filtered.sort((a, b) => {
- const aValue = a[sortField]
- const bValue = b[sortField]
-
- if (typeof aValue === 'string' && typeof bValue === 'string') {
- return sortOrder === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue)
- }
-
- if (typeof aValue === 'number' && typeof bValue === 'number') {
- return sortOrder === 'asc' ? aValue - bValue : bValue - aValue
- }
-
- return 0
- })
-
- return filtered
- }, [searchTerm, sortField, sortOrder, storeData])
-
- // Подсчет общих сумм
- const totals = useMemo(() => {
- return filteredAndSortedStores.reduce(
- (acc, store) => ({
- products: acc.products + store.products,
- goods: acc.goods + store.goods,
- defects: acc.defects + store.defects,
- sellerSupplies: acc.sellerSupplies + store.sellerSupplies,
- pvzReturns: acc.pvzReturns + store.pvzReturns,
- productsChange: acc.productsChange + store.productsChange,
- goodsChange: acc.goodsChange + store.goodsChange,
- defectsChange: acc.defectsChange + store.defectsChange,
- sellerSuppliesChange: acc.sellerSuppliesChange + store.sellerSuppliesChange,
- pvzReturnsChange: acc.pvzReturnsChange + store.pvzReturnsChange,
- }),
- {
- products: 0,
- goods: 0,
- defects: 0,
- sellerSupplies: 0,
- pvzReturns: 0,
- productsChange: 0,
- goodsChange: 0,
- defectsChange: 0,
- sellerSuppliesChange: 0,
- pvzReturnsChange: 0,
- },
- )
- }, [filteredAndSortedStores])
-
- const formatNumber = (num: number) => {
- return num.toLocaleString('ru-RU')
- }
-
- const formatChange = (change: number) => {
- const sign = change > 0 ? '+' : ''
- return `${sign}${change}`
- }
-
- const toggleStoreExpansion = (storeId: string) => {
- const newExpanded = new Set(expandedStores)
- if (newExpanded.has(storeId)) {
- newExpanded.delete(storeId)
- } else {
- newExpanded.add(storeId)
- }
- setExpandedStores(newExpanded)
- }
-
- const toggleItemExpansion = (itemId: string) => {
- const newExpanded = new Set(expandedItems)
- if (newExpanded.has(itemId)) {
- newExpanded.delete(itemId)
- } else {
- newExpanded.add(itemId)
- }
- setExpandedItems(newExpanded)
- }
-
- const handleSort = (field: keyof StoreData) => {
- if (sortField === field) {
- setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
- } else {
- setSortField(field)
- setSortOrder('asc')
- }
- }
-
- // Компонент компактной статистической карточки
- const StatCard = ({
- title,
- icon: Icon,
- current,
- change,
- percentChange,
- description,
- onClick,
- }: {
- title: string
- icon: React.ComponentType<{ className?: string }>
- current: number
- change: number
- percentChange?: number
- description: string
- onClick?: () => void
- }) => {
- // Используем percentChange из GraphQL, если доступно, иначе вычисляем локально
- const displayPercentChange =
- percentChange !== undefined && percentChange !== null && !isNaN(percentChange)
- ? percentChange
- : current > 0
- ? (change / current) * 100
- : 0
-
- return (
-
-
-
- {/* Процентное изменение - всегда показываем */}
-
- {change >= 0 ? (
-
- ) : (
-
- )}
- = 0 ? 'text-green-400' : 'text-red-400'}`}>
- {displayPercentChange.toFixed(1)}%
-
-
-
-
-
{formatNumber(current)}
- {/* Изменения - всегда показываем */}
-
-
= 0 ? 'bg-green-500/20' : 'bg-red-500/20'
- }`}
- >
- = 0 ? 'text-green-400' : 'text-red-400'}`}>
- {change >= 0 ? '+' : ''}
- {change}
-
-
-
-
-
{description}
- {onClick && (
-
-
-
- )}
-
- )
- }
-
- // Компонент заголовка таблицы
- const TableHeader = ({
- field,
- children,
- sortable = false,
- }: {
- field?: keyof StoreData
- children: React.ReactNode
- sortable?: boolean
- }) => (
- handleSort(field) : undefined}
- >
-
{children}
- {sortable && field && (
-
- )}
- {field === 'pvzReturns' && (
-
- )}
-
- )
-
- // Индикатор загрузки
- if (counterpartiesLoading || ordersLoading || productsLoading || sellerSuppliesLoading) {
- return (
-
-
-
-
-
-
Загрузка данных склада...
-
-
-
- )
- }
-
- // Индикатор ошибки
- if (counterpartiesError || ordersError || productsError) {
- return (
-
-
-
-
-
-
Ошибка загрузки данных склада
-
- {counterpartiesError?.message || ordersError?.message || productsError?.message}
-
-
-
-
- )
- }
-
- // Если показываем заявки на возврат, отображаем соответствующий компонент
- if (showReturnClaims) {
- return (
-
-
-
-
- setShowReturnClaims(false)} />
-
-
-
- )
- }
-
- return (
-
-
-
- {/* Компактная статичная верхняя секция со статистикой - максимум 30% экрана */}
-
-
-
-
Статистика склада
- {/* Индикатор обновления данных */}
-
-
-
- Обновлено из поставок
- {supplyOrders.filter((o) => o.status === 'DELIVERED').length > 0 && (
-
- {supplyOrders.filter((o) => o.status === 'DELIVERED').length} поставок получено
-
- )}
-
-
-
-
-
-
-
-
- setShowReturnClaims(true)}
- />
-
- router.push('/fulfillment-warehouse/supplies')}
- />
-
-
-
-
- {/* Основная скроллируемая часть - оставшиеся 70% экрана */}
-
-
- {/* Компактная шапка таблицы - максимум 10% экрана */}
-
-
-
-
- Детализация по Магазинам
-
-
-
- {/* Компактный поиск */}
-
-
-
- setSearchTerm(e.target.value)}
- className="pl-8 h-8 text-sm glass-input text-white placeholder:text-white/40 flex-1"
- />
-
-
-
-
-
- {filteredAndSortedStores.length} магазинов
-
-
-
-
- {/* Фиксированные заголовки таблицы - Уровень 1 (Поставщики) */}
-
-
-
- № / Магазин
-
-
- Продукты
-
-
- Товары
-
-
- Брак
-
-
- Расходники селлера
-
-
- Возвраты с ПВЗ
-
-
-
-
- {/* Строка с суммами - Уровень 1 (Поставщики) */}
-
-
-
- ИТОГО ({filteredAndSortedStores.length})
-
-
-
-
{formatNumber(totals.products)}
-
- {totals.productsChange >= 0 ? (
-
- ) : (
-
- )}
- = 0 ? 'text-green-400' : 'text-red-400'
- }`}
- >
- {totals.products > 0 ? ((totals.productsChange / totals.products) * 100).toFixed(1) : '0.0'}%
-
-
-
- {showAdditionalValues && (
-
-
-
- +0 {/* ТЕСТ: Временно захардкожено для проверки */}
-
-
-
-
- -0 {/* ТЕСТ: Временно захардкожено для проверки */}
-
-
-
- {Math.abs(totals.productsChange)}
-
-
- )}
-
-
-
-
{formatNumber(totals.goods)}
-
- {totals.goodsChange >= 0 ? (
-
- ) : (
-
- )}
- = 0 ? 'text-green-400' : 'text-red-400'
- }`}
- >
- {totals.goods > 0 ? ((totals.goodsChange / totals.goods) * 100).toFixed(1) : '0.0'}%
-
-
-
- {showAdditionalValues && (
-
-
-
- +0 {/* Нет реальных данных о готовых товарах */}
-
-
-
-
- -0 {/* Нет реальных данных о готовых товарах */}
-
-
-
- {Math.abs(totals.goodsChange)}
-
-
- )}
-
-
-
-
{formatNumber(totals.defects)}
-
- {totals.defectsChange >= 0 ? (
-
- ) : (
-
- )}
- = 0 ? 'text-green-400' : 'text-red-400'
- }`}
- >
- {totals.defects > 0 ? ((totals.defectsChange / totals.defects) * 100).toFixed(1) : '0.0'}%
-
-
-
- {showAdditionalValues && (
-
-
-
- +0 {/* Нет реальных данных о браке */}
-
-
-
-
- -0 {/* Нет реальных данных о браке */}
-
-
-
- {Math.abs(totals.defectsChange)}
-
-
- )}
-
-
-
-
{formatNumber(totals.sellerSupplies)}
-
- {totals.sellerSuppliesChange >= 0 ? (
-
- ) : (
-
- )}
- = 0 ? 'text-green-400' : 'text-red-400'
- }`}
- >
- {totals.sellerSupplies > 0
- ? ((totals.sellerSuppliesChange / totals.sellerSupplies) * 100).toFixed(1)
- : '0.0'}
- %
-
-
-
- {showAdditionalValues && (
-
-
-
- +{Math.max(totals.sellerSuppliesChange, 0)}
-
-
-
-
- -{Math.max(-totals.sellerSuppliesChange, 0)}
-
-
-
- {Math.abs(totals.sellerSuppliesChange)}
-
-
- )}
-
-
-
-
{formatNumber(totals.pvzReturns)}
-
- {totals.pvzReturnsChange >= 0 ? (
-
- ) : (
-
- )}
- = 0 ? 'text-green-400' : 'text-red-400'
- }`}
- >
- {totals.pvzReturns > 0
- ? ((totals.pvzReturnsChange / totals.pvzReturns) * 100).toFixed(1)
- : '0.0'}
- %
-
-
-
- {showAdditionalValues && (
-
-
-
- +0 {/* Нет реальных данных о возвратах с ПВЗ */}
-
-
-
-
- -0 {/* Нет реальных данных о возвратах с ПВЗ */}
-
-
-
- {Math.abs(totals.pvzReturnsChange)}
-
-
- )}
-
-
-
-
- {/* Скроллируемый контент таблицы - оставшееся пространство */}
-
- {filteredAndSortedStores.length === 0 ? (
-
-
-
-
- {sellerPartners.length === 0
- ? 'Нет магазинов'
- : allProducts.length === 0
- ? 'Нет товаров на складе'
- : 'Магазины не найдены'}
-
-
- {sellerPartners.length === 0
- ? 'Добавьте магазины для отображения данных склада'
- : allProducts.length === 0
- ? 'Добавьте товары на склад для отображения данных'
- : searchTerm
- ? 'Попробуйте изменить поисковый запрос'
- : 'Данные о магазинах будут отображены здесь'}
-
-
-
- ) : (
- filteredAndSortedStores.map((store, index) => {
- const colorScheme = getColorScheme(store.id)
- return (
-
- {/* Основная строка поставщика */}
-
toggleStoreExpansion(store.id)}
- >
-
-
{filteredAndSortedStores.length - index}
-
-
- {store.avatar && }
-
- {getInitials(store.name)}
-
-
-
-
-
-
-
-
-
- {formatNumber(store.products)}
-
- {showAdditionalValues && (
-
-
-
- +{Math.max(0, store.productsChange)} {/* Поступило товаров */}
-
-
-
-
- -{Math.max(0, -store.productsChange)} {/* Использовано товаров */}
-
-
-
-
- {Math.abs(store.productsChange)}
-
-
-
- )}
-
-
-
-
-
-
{formatNumber(store.goods)}
- {showAdditionalValues && (
-
-
-
- +0 {/* Нет реальных данных о готовых товарах */}
-
-
-
-
- -0 {/* Нет реальных данных о готовых товарах */}
-
-
-
- {Math.abs(store.goodsChange)}
-
-
- )}
-
-
-
-
-
-
{formatNumber(store.defects)}
- {showAdditionalValues && (
-
-
-
- +0 {/* Нет реальных данных о браке */}
-
-
-
-
- -0 {/* Нет реальных данных о браке */}
-
-
-
-
- {Math.abs(store.defectsChange)}
-
-
-
- )}
-
-
-
-
-
-
- {formatNumber(store.sellerSupplies)}
-
- {showAdditionalValues && (
-
-
-
- +{Math.max(0, store.sellerSuppliesChange)} {/* Поступило расходников */}
-
-
-
-
- -{Math.max(0, -store.sellerSuppliesChange)} {/* Использовано расходников */}
-
-
-
-
- {Math.abs(store.sellerSuppliesChange)}
-
-
-
- )}
-
-
-
-
-
-
- {formatNumber(store.pvzReturns)}
-
- {showAdditionalValues && (
-
-
-
- +0 {/* Нет реальных данных о возвратах с ПВЗ */}
-
-
-
-
- -0 {/* Нет реальных данных о возвратах с ПВЗ */}
-
-
-
-
- {Math.abs(store.pvzReturnsChange)}
-
-
-
- )}
-
-
-
-
- {/* Второй уровень - детализация по товарам */}
- {expandedStores.has(store.id) && (
-
- {/* Статическая часть - заголовки столбцов второго уровня */}
-
-
-
- Наименование
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
-
- {/* Динамическая часть - данные по товарам (скроллируемая) */}
-
- {store.items?.map((item) => (
-
- {/* Основная строка товара */}
-
toggleItemExpansion(item.id)}
- >
-
- {/* Наименование */}
-
-
-
-
-
{item.name}
- {item.variants && item.variants.length > 0 && (
-
- {item.variants.length} вар.
-
- )}
-
-
{item.article}
-
-
-
- {/* Продукты */}
-
-
- {formatNumber(item.productQuantity)}
-
-
- {item.productPlace || '-'}
-
-
-
- {/* Товары */}
-
-
- {formatNumber(item.goodsQuantity)}
-
-
- {item.goodsPlace || '-'}
-
-
-
- {/* Брак */}
-
-
- {formatNumber(item.defectsQuantity)}
-
-
- {item.defectsPlace || '-'}
-
-
-
- {/* Расходники селлера */}
-
-
-
-
- {formatNumber(item.sellerSuppliesQuantity)}
-
-
-
-
-
Расходники селлеров:
- {item.sellerSuppliesOwners && item.sellerSuppliesOwners.length > 0 ? (
-
- {item.sellerSuppliesOwners.map((owner, i) => (
-
- ))}
-
- ) : (
-
Нет данных о владельцах
- )}
-
-
-
-
- {item.sellerSuppliesPlace || '-'}
-
-
-
- {/* Возвраты с ПВЗ */}
-
-
- {formatNumber(item.pvzReturnsQuantity)}
-
-
- {item.pvzReturnsPlace || '-'}
-
-
-
-
-
- {/* Третий уровень - варианты товара */}
- {expandedItems.has(item.id) && item.variants && item.variants.length > 0 && (
-
- {/* Заголовки для вариантов */}
-
-
-
- Вариант
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
- Кол-во
-
-
- Место
-
-
-
-
-
- {/* Данные по вариантам */}
-
- {item.variants.map((variant) => (
-
-
- {/* Название варианта */}
-
-
- {/* Продукты */}
-
-
- {formatNumber(variant.productQuantity)}
-
-
- {variant.productPlace || '-'}
-
-
-
- {/* Товары */}
-
-
- {formatNumber(variant.goodsQuantity)}
-
-
- {variant.goodsPlace || '-'}
-
-
-
- {/* Брак */}
-
-
- {formatNumber(variant.defectsQuantity)}
-
-
- {variant.defectsPlace || '-'}
-
-
-
- {/* Расходники селлера */}
-
-
-
-
- {formatNumber(variant.sellerSuppliesQuantity)}
-
-
-
-
-
- Расходники селлеров:
-
- {variant.sellerSuppliesOwners &&
- variant.sellerSuppliesOwners.length > 0 ? (
-
- {variant.sellerSuppliesOwners.map((owner, i) => (
-
- ))}
-
- ) : (
-
Нет данных о владельцах
- )}
-
-
-
-
- {variant.sellerSuppliesPlace || '-'}
-
-
-
- {/* Возвраты с ПВЗ */}
-
-
- {formatNumber(variant.pvzReturnsQuantity)}
-
-
- {variant.pvzReturnsPlace || '-'}
-
-
-
-
- ))}
-
-
- )}
-
- ))}
-
-
- )}
-
- )
- })
- )}
-
-
-
-
-
- )
-}
diff --git a/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard/index.tsx b/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard/index.tsx
index ffae3c3..ab6f954 100644
--- a/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard/index.tsx
+++ b/src/components/fulfillment-warehouse/fulfillment-warehouse-dashboard/index.tsx
@@ -34,7 +34,7 @@ import {
GET_FULFILLMENT_WAREHOUSE_STATS, // Статистика склада с изменениями за сутки
GET_SUPPLY_MOVEMENTS, // Движения товаров (прибыло/убыло)
} from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useRealtime } from '@/hooks/useRealtime'
import { useSidebar } from '@/hooks/useSidebar'
@@ -115,7 +115,7 @@ interface WarehouseStats {
export function FulfillmentWarehouseDashboard() {
const router = useRouter()
const { getSidebarMargin } = useSidebar()
- const { user: _user } = useAuth()
+ const { user: _user } = useAuthContext()
// Состояния для поиска и фильтрации
const [searchTerm, setSearchTerm] = useState('')
diff --git a/src/components/home/fulfillment-home-page.tsx b/src/components/home/fulfillment-home-page.tsx
index 336aefc..ae83393 100644
--- a/src/components/home/fulfillment-home-page.tsx
+++ b/src/components/home/fulfillment-home-page.tsx
@@ -4,11 +4,11 @@ import { Factory } from 'lucide-react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
export function FulfillmentHomePage() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const getOrganizationName = () => {
diff --git a/src/components/home/logist-home-page.tsx b/src/components/home/logist-home-page.tsx
index 065264e..879e61d 100644
--- a/src/components/home/logist-home-page.tsx
+++ b/src/components/home/logist-home-page.tsx
@@ -4,11 +4,11 @@ import { Truck } from 'lucide-react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
export function LogistHomePage() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const getOrganizationName = () => {
diff --git a/src/components/home/seller-home-page.tsx b/src/components/home/seller-home-page.tsx
index 6a8b09c..6600d7a 100644
--- a/src/components/home/seller-home-page.tsx
+++ b/src/components/home/seller-home-page.tsx
@@ -4,11 +4,11 @@ import { Building2 } from 'lucide-react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
export function SellerHomePage() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const getOrganizationName = () => {
diff --git a/src/components/home/wholesale-home-page.tsx b/src/components/home/wholesale-home-page.tsx
index 58927de..f6e8821 100644
--- a/src/components/home/wholesale-home-page.tsx
+++ b/src/components/home/wholesale-home-page.tsx
@@ -4,11 +4,11 @@ import { Package } from 'lucide-react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
export function WholesaleHomePage() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const getOrganizationName = () => {
diff --git a/src/components/layout/app-shell.tsx b/src/components/layout/app-shell.tsx
index 10a50cc..cfa0170 100644
--- a/src/components/layout/app-shell.tsx
+++ b/src/components/layout/app-shell.tsx
@@ -4,9 +4,7 @@ import dynamic from 'next/dynamic'
import { usePathname } from 'next/navigation'
import { useEffect, useState } from 'react'
-import { useAuth } from '@/hooks/useAuth'
-import { useQuery } from '@apollo/client'
-import { GET_ME } from '@/graphql/queries'
+import { useAuthContext } from '@/contexts/AuthContext'
// Рендерим сайдбар только на клиенте, отключаем SSR, чтобы избежать гидратационных расхождений
const Sidebar = dynamic(() => import('@/components/dashboard/sidebar').then((m) => m.Sidebar), {
@@ -15,16 +13,8 @@ const Sidebar = dynamic(() => import('@/components/dashboard/sidebar').then((m)
export function AppShell({ children }: { children: React.ReactNode }) {
const pathname = usePathname()
- const { isAuthenticated, isLoading } = useAuth()
+ const { isAuthenticated, isLoading, user } = useAuthContext()
const [mounted, setMounted] = useState(false)
-
- // Используем GET_ME напрямую чтобы получить актуальные данные
- const { data: meData } = useQuery(GET_ME, {
- skip: !isAuthenticated,
- fetchPolicy: 'cache-first', // Используем кеш Apollo
- })
-
- const user = meData?.me || null
useEffect(() => {
setMounted(true)
@@ -45,11 +35,11 @@ export function AppShell({ children }: { children: React.ReactNode }) {
isLoading,
isAuthenticated,
hasUser: !!user,
- hasDirectMeData: !!meData?.me,
+ hasUserFromContext: !!user,
hideSidebar,
pathname,
userPhone: user?.phone,
- organizationType: user?.organization?.type
+ organizationType: user?.organization?.type,
})
// Дополнительная диагностика
@@ -60,7 +50,7 @@ export function AppShell({ children }: { children: React.ReactNode }) {
notMounted: !mounted,
hideByRoute,
isLoading,
- notAuthenticated: !isAuthenticated
+ notAuthenticated: !isAuthenticated,
})
}
}
diff --git a/src/components/logistics-orders/logistics-orders-dashboard.tsx b/src/components/logistics-orders/logistics-orders-dashboard.tsx
index 229ac63..f0f6ee7 100644
--- a/src/components/logistics-orders/logistics-orders-dashboard.tsx
+++ b/src/components/logistics-orders/logistics-orders-dashboard.tsx
@@ -29,7 +29,7 @@ import { LOGISTICS_CONFIRM_ORDER, LOGISTICS_REJECT_ORDER } from '@/graphql/mutat
import { LOGISTICS_CONFIRM_CONSUMABLE_SUPPLY, LOGISTICS_REJECT_CONSUMABLE_SUPPLY } from '@/graphql/mutations/logistics-consumables-v2'
import { GET_SUPPLY_ORDERS } from '@/graphql/queries'
import { GET_MY_LOGISTICS_CONSUMABLE_SUPPLIES } from '@/graphql/queries/logistics-consumables-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
interface SupplyOrder {
@@ -87,7 +87,7 @@ interface SupplyOrder {
export function LogisticsOrdersDashboard() {
const { getSidebarMargin } = useSidebar()
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [expandedOrders, setExpandedOrders] = useState>(new Set())
const [rejectReason, setRejectReason] = useState('')
const [showRejectModal, setShowRejectModal] = useState(null)
diff --git a/src/components/market/market-counterparties/index.tsx b/src/components/market/market-counterparties/index.tsx
index cc8bd06..2f82584 100644
--- a/src/components/market/market-counterparties/index.tsx
+++ b/src/components/market/market-counterparties/index.tsx
@@ -12,14 +12,14 @@ import { toast } from 'sonner'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
// Hooks
+import { CounterpartiesListBlock } from './blocks/CounterpartiesListBlock'
+import { IncomingRequestsBlock } from './blocks/IncomingRequestsBlock'
+import { OutgoingRequestsBlock } from './blocks/OutgoingRequestsBlock'
import { useCounterpartyActions } from './hooks/useCounterpartyActions'
import { useCounterpartyData } from './hooks/useCounterpartyData'
import { useCounterpartyFilters } from './hooks/useCounterpartyFilters'
// UI Blocks
-import { CounterpartiesListBlock } from './blocks/CounterpartiesListBlock'
-import { IncomingRequestsBlock } from './blocks/IncomingRequestsBlock'
-import { OutgoingRequestsBlock } from './blocks/OutgoingRequestsBlock'
// Types
import type { Organization } from './types'
diff --git a/src/components/messenger/messenger-attachments.tsx b/src/components/messenger/messenger-attachments.tsx
index b4454f1..ccd6c5b 100644
--- a/src/components/messenger/messenger-attachments.tsx
+++ b/src/components/messenger/messenger-attachments.tsx
@@ -11,7 +11,7 @@ import { ImageLightbox } from '@/components/ui/image-lightbox'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { VoicePlayer } from '@/components/ui/voice-player'
import { GET_MESSAGES } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useRealtime } from '@/hooks/useRealtime'
interface Organization {
@@ -46,7 +46,7 @@ interface MessengerAttachmentsProps {
}
export function MessengerAttachments({ counterparty, onViewChange }: MessengerAttachmentsProps) {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [activeTab, setActiveTab] = useState('all')
const [lightboxImage, setLightboxImage] = useState<{ url: string; fileName: string; fileSize?: number } | null>(null)
diff --git a/src/components/messenger/messenger-chat.tsx b/src/components/messenger/messenger-chat.tsx
index d4d70b9..e0d1810 100644
--- a/src/components/messenger/messenger-chat.tsx
+++ b/src/components/messenger/messenger-chat.tsx
@@ -21,7 +21,7 @@ import {
MARK_MESSAGES_AS_READ,
} from '@/graphql/mutations'
import { GET_MESSAGES, GET_CONVERSATIONS } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useRealtime } from '@/hooks/useRealtime'
import { MessengerAttachments } from './messenger-attachments'
@@ -62,7 +62,7 @@ interface MessengerChatProps {
}
export function MessengerChat({ counterparty, onMessagesRead }: MessengerChatProps) {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [message, setMessage] = useState('')
const [currentView, setCurrentView] = useState<'chat' | 'attachments'>('chat')
const messagesEndRef = useRef(null)
diff --git a/src/components/seller-statistics/seller-statistics-dashboard.tsx b/src/components/seller-statistics/seller-statistics-dashboard.tsx
index b4884ef..805e3f8 100644
--- a/src/components/seller-statistics/seller-statistics-dashboard.tsx
+++ b/src/components/seller-statistics/seller-statistics-dashboard.tsx
@@ -10,14 +10,14 @@ import { SalesTab } from '@/components/seller-statistics/sales-tab'
import { Card } from '@/components/ui/card'
import { DateRangePicker } from '@/components/ui/date-picker'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
+import { useAuthContext } from '@/contexts/AuthContext'
import { SAVE_SELLER_STATS_CACHE } from '@/graphql/mutations'
import { GET_SELLER_STATS_CACHE } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
import { useSidebar } from '@/hooks/useSidebar'
const SellerStatisticsDashboard = React.memo(() => {
const { getSidebarMargin } = useSidebar()
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [selectedPeriod, setSelectedPeriod] = useState('week')
const [useCustomDates, setUseCustomDates] = useState(false)
const [startDate, setStartDate] = useState('')
diff --git a/src/components/seller-statistics/simple-advertising-table.tsx b/src/components/seller-statistics/simple-advertising-table.tsx
index 706e340..b36955d 100644
--- a/src/components/seller-statistics/simple-advertising-table.tsx
+++ b/src/components/seller-statistics/simple-advertising-table.tsx
@@ -13,7 +13,7 @@ import { useState } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { GET_WB_WAREHOUSE_DATA } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
interface WBStock {
nmId: number
@@ -105,7 +105,7 @@ export function SimpleAdvertisingTable({
onUpdateExternalAd,
onGenerateLink,
}: SimpleAdvertisingTableProps) {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [showAddForm, setShowAddForm] = useState(null)
const [showProductList, setShowProductList] = useState(null)
const [searchTerm, setSearchTerm] = useState('')
diff --git a/src/components/services/logistics-tab.tsx b/src/components/services/logistics-tab.tsx
index 68409c7..06d5b52 100644
--- a/src/components/services/logistics-tab.tsx
+++ b/src/components/services/logistics-tab.tsx
@@ -27,7 +27,7 @@ import {
UPDATE_FULFILLMENT_LOGISTICS,
DELETE_FULFILLMENT_LOGISTICS,
} from '@/graphql/queries/fulfillment-services-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { WildberriesService } from '@/services/wildberries-service'
interface LogisticsRoute {
@@ -78,7 +78,7 @@ const BASE_LOCATIONS = {
}
export function LogisticsTab() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [editableLogistics, setEditableLogistics] = useState([])
const [isSaving, setIsSaving] = useState(false)
const [isInitialized, setIsInitialized] = useState(false)
diff --git a/src/components/services/services-tab.tsx b/src/components/services/services-tab.tsx
index 4e90f76..f8f5db3 100644
--- a/src/components/services/services-tab.tsx
+++ b/src/components/services/services-tab.tsx
@@ -27,7 +27,7 @@ import {
UPDATE_FULFILLMENT_SERVICE,
DELETE_FULFILLMENT_SERVICE,
} from '@/graphql/queries/fulfillment-services-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
interface Service {
id: string
@@ -56,7 +56,7 @@ interface PendingChange extends EditableService {
}
export function ServicesTab() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [editableServices, setEditableServices] = useState([])
const [pendingChanges, setPendingChanges] = useState([])
const [isSaving, setIsSaving] = useState(false)
diff --git a/src/components/services/supplies-tab.tsx b/src/components/services/supplies-tab.tsx
index ea2f2ad..9bc460c 100644
--- a/src/components/services/supplies-tab.tsx
+++ b/src/components/services/supplies-tab.tsx
@@ -12,7 +12,7 @@ import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
// ИСПРАВЛЕНО: Используем новый V2 запрос для расходников фулфилмента
import { GET_MY_FULFILLMENT_CONSUMABLES_V2, UPDATE_FULFILLMENT_CONSUMABLE } from '@/graphql/queries/fulfillment-services-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
// ИСПРАВЛЕНО: Интерфейс для V2 расходников фулфилмента
interface FulfillmentConsumable {
@@ -66,7 +66,7 @@ interface EditableFulfillmentConsumable {
// PendingChange interface no longer needed
export function SuppliesTab() {
- const { user, isCheckingAuth } = useAuth()
+ const { user, isLoading: isCheckingAuth } = useAuthContext()
const [editableConsumables, setEditableConsumables] = useState([])
const [isSaving, setIsSaving] = useState(false)
const [isInitialized, setIsInitialized] = useState(false)
diff --git a/src/components/supplier-orders/supplier-orders-tabs-v2.tsx b/src/components/supplier-orders/supplier-orders-tabs-v2.tsx
index 7d76b6c..d6393eb 100644
--- a/src/components/supplier-orders/supplier-orders-tabs-v2.tsx
+++ b/src/components/supplier-orders/supplier-orders-tabs-v2.tsx
@@ -13,7 +13,7 @@ import { SUPPLIER_APPROVE_CONSUMABLE_SUPPLY, SUPPLIER_REJECT_CONSUMABLE_SUPPLY,
import { UPDATE_SELLER_GOODS_SUPPLY_STATUS, UPDATE_SUPPLY_VOLUME_V2, UPDATE_SUPPLY_PACKAGES_V2, GET_MY_SELLER_GOODS_SUPPLY_REQUESTS } from '@/graphql/mutations/seller-goods-v2'
import { GET_MY_SUPPLIER_CONSUMABLE_SUPPLIES } from '@/graphql/queries/fulfillment-consumables-v2'
import { UPDATE_SELLER_SUPPLY_STATUS, GET_MY_SELLER_SUPPLY_REQUESTS } from '@/graphql/queries/seller-consumables-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { SupplierOrderStats } from './supplier-order-stats'
import { SupplierOrdersSearch } from './supplier-orders-search'
@@ -72,7 +72,7 @@ interface SupplyOrder {
}
export function SupplierOrdersTabsV2() {
- const { user: _user } = useAuth()
+ const { user: _user } = useAuthContext()
const [activeTab, setActiveTab] = useState('new')
const [searchQuery, setSearchQuery] = useState('')
const [sortField, setSortField] = useState('createdAt')
diff --git a/src/components/supplies/consumables-supplies/consumables-supplies-tab.tsx b/src/components/supplies/consumables-supplies/consumables-supplies-tab.tsx
index 8049395..dfb05b6 100644
--- a/src/components/supplies/consumables-supplies/consumables-supplies-tab.tsx
+++ b/src/components/supplies/consumables-supplies/consumables-supplies-tab.tsx
@@ -7,7 +7,7 @@ import React, { useState } from 'react'
import { Badge } from '@/components/ui/badge'
import { Card } from '@/components/ui/card'
import { GET_SUPPLY_ORDERS } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
// Типы данных для заказов поставок расходников
interface SupplyOrderItem {
@@ -55,7 +55,7 @@ interface SupplyOrder {
export function SuppliesConsumablesTab() {
const [expandedOrders, setExpandedOrders] = useState>(new Set())
- const { user } = useAuth()
+ const { user } = useAuthContext()
// Загружаем заказы поставок
const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS, {
diff --git a/src/components/supplies/create-consumables-supply-page.tsx b/src/components/supplies/create-consumables-supply-page.tsx
index a89af99..6d8e2ca 100644
--- a/src/components/supplies/create-consumables-supply-page.tsx
+++ b/src/components/supplies/create-consumables-supply-page.tsx
@@ -15,7 +15,7 @@ import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { GET_MY_COUNTERPARTIES, GET_ORGANIZATION_PRODUCTS } from '@/graphql/queries'
import { CREATE_SELLER_CONSUMABLE_SUPPLY, GET_MY_SELLER_CONSUMABLE_SUPPLIES } from '@/graphql/queries/seller-consumables-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
interface ConsumableSupplier {
@@ -60,7 +60,7 @@ interface SelectedConsumable {
export function CreateConsumablesSupplyPage() {
const router = useRouter()
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const [selectedSupplier, setSelectedSupplier] = useState(null)
const [selectedConsumables, setSelectedConsumables] = useState([])
diff --git a/src/components/supplies/create-suppliers/index.tsx b/src/components/supplies/create-suppliers/index.tsx
index a30d628..c070dba 100644
--- a/src/components/supplies/create-suppliers/index.tsx
+++ b/src/components/supplies/create-suppliers/index.tsx
@@ -13,7 +13,7 @@ import React, { useCallback, useState, useEffect } from 'react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { Button } from '@/components/ui/button'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
import { CartBlock } from './blocks/CartBlock'
@@ -28,7 +28,7 @@ import type { GoodsSupplier, GoodsProduct, ProductRecipe } from './types/supply-
export function CreateSuppliersSupplyPage() {
const router = useRouter()
- const { user: _user } = useAuth()
+ const { user: _user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
// 1. ХУКА ВЫБОРА ПОСТАВЩИКОВ
diff --git a/src/components/supplies/direct-supply-creation.tsx b/src/components/supplies/direct-supply-creation.tsx
index 0a6f975..d4cc2c4 100644
--- a/src/components/supplies/direct-supply-creation.tsx
+++ b/src/components/supplies/direct-supply-creation.tsx
@@ -31,7 +31,7 @@ import {
} from 'lucide-react'
import { WildberriesService } from '@/services/wildberries-service'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useQuery, useMutation } from '@apollo/client'
@@ -122,7 +122,7 @@ export function DirectSupplyCreation({
onVolumeChange,
onSuppliersChange,
}: DirectSupplyCreationProps) {
- const { user } = useAuth()
+ const { user } = useAuthContext()
// Новые состояния для блока создания поставки
const [deliveryDate, setDeliveryDate] = useState('')
diff --git a/src/components/supplies/direct-supply-creation/hooks/useWildberriesProducts.ts b/src/components/supplies/direct-supply-creation/hooks/useWildberriesProducts.ts
index b2d0838..024936e 100644
--- a/src/components/supplies/direct-supply-creation/hooks/useWildberriesProducts.ts
+++ b/src/components/supplies/direct-supply-creation/hooks/useWildberriesProducts.ts
@@ -7,7 +7,7 @@
import { useState, useEffect, useCallback } from 'react'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { WildberriesService } from '@/services/wildberries-service'
import { WildberriesCard } from '@/types/supplies'
@@ -164,7 +164,7 @@ const getMockCards = (): WildberriesCard[] => [
]
export function useWildberriesProducts(): UseWildberriesProductsReturn {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [searchTerm, setSearchTerm] = useState('')
const [loading, setLoading] = useState(false)
diff --git a/src/components/supplies/fulfillment-supplies/all-supplies-tab.tsx b/src/components/supplies/fulfillment-supplies/all-supplies-tab.tsx
index 94c5111..945663d 100644
--- a/src/components/supplies/fulfillment-supplies/all-supplies-tab.tsx
+++ b/src/components/supplies/fulfillment-supplies/all-supplies-tab.tsx
@@ -4,7 +4,7 @@ import { Package } from 'lucide-react'
import React from 'react'
import { Card } from '@/components/ui/card'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { MultiLevelSuppliesTable } from '../multilevel-supplies-table'
@@ -15,7 +15,7 @@ interface AllSuppliesTabProps {
}
export function AllSuppliesTab({ pendingSupplyOrders = 0, goodsSupplies = [], loading = false }: AllSuppliesTabProps) {
- const { user } = useAuth()
+ const { user } = useAuthContext()
// ✅ ЕДИНАЯ ТАБЛИЦА ПОСТАВОК ТОВАРОВ согласно rules2.md 9.5.3
return (
diff --git a/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx b/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx
index 913925f..dd28c10 100644
--- a/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx
+++ b/src/components/supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx
@@ -3,7 +3,7 @@
import React, { useState, useEffect } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { AllSuppliesTab } from './all-supplies-tab'
import { FulfillmentGoodsTab } from './fulfillment-goods-tab'
@@ -17,7 +17,7 @@ interface FulfillmentSuppliesTabProps {
export function FulfillmentSuppliesTab({ defaultSubTab, pendingSupplyOrders = 0 }: FulfillmentSuppliesTabProps) {
const [activeSubTab, setActiveSubTab] = useState('all')
- const { user } = useAuth()
+ const { user } = useAuthContext()
// Устанавливаем активную подвкладку при получении defaultSubTab
useEffect(() => {
diff --git a/src/components/supplies/fulfillment-supplies/pvz-returns-tab.tsx b/src/components/supplies/fulfillment-supplies/pvz-returns-tab.tsx
index 3359329..ad0d055 100644
--- a/src/components/supplies/fulfillment-supplies/pvz-returns-tab.tsx
+++ b/src/components/supplies/fulfillment-supplies/pvz-returns-tab.tsx
@@ -19,7 +19,7 @@ import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { WildberriesService, type WBClaim, type WBClaimsResponse } from '@/services/wildberries-service'
// Интерфейс для обработанных данных возврата
@@ -41,7 +41,7 @@ interface ProcessedClaim {
}
export function PvzReturnsTab() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [searchTerm, setSearchTerm] = useState('')
const [statusFilter, setStatusFilter] = useState('all')
const [archiveFilter, setArchiveFilter] = useState(false)
diff --git a/src/components/supplies/fulfillment-supplies/real-supply-orders-tab.tsx b/src/components/supplies/fulfillment-supplies/real-supply-orders-tab.tsx
index 827c938..a8e9828 100644
--- a/src/components/supplies/fulfillment-supplies/real-supply-orders-tab.tsx
+++ b/src/components/supplies/fulfillment-supplies/real-supply-orders-tab.tsx
@@ -34,7 +34,7 @@ import {
SUPPLIER_SHIP_ORDER,
} from '@/graphql/mutations'
import { GET_SUPPLY_ORDERS } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useRealtime } from '@/hooks/useRealtime'
// Типы для данных заказов
@@ -163,7 +163,7 @@ export function RealSupplyOrdersTab() {
const [searchTerm, setSearchTerm] = useState('')
const [sortField, setSortField] = useState('createdAt')
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { data, loading, error, refetch } = useQuery(GET_SUPPLY_ORDERS, {
fetchPolicy: 'cache-and-network',
diff --git a/src/components/supplies/fulfillment-supplies/seller-supply-orders-tab.tsx b/src/components/supplies/fulfillment-supplies/seller-supply-orders-tab.tsx
index 3b9a068..6c18534 100644
--- a/src/components/supplies/fulfillment-supplies/seller-supply-orders-tab.tsx
+++ b/src/components/supplies/fulfillment-supplies/seller-supply-orders-tab.tsx
@@ -18,7 +18,7 @@ import React, { useState } from 'react'
import { Badge } from '@/components/ui/badge'
import { Card } from '@/components/ui/card'
import { GET_MY_SELLER_CONSUMABLE_SUPPLIES } from '@/graphql/queries/seller-consumables-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
// Типы данных для заказов поставок расходников
interface SupplyOrderItem {
@@ -80,7 +80,7 @@ interface SupplyOrder {
export function SellerSupplyOrdersTab() {
const [expandedOrders, setExpandedOrders] = useState>(new Set())
- const { user } = useAuth()
+ const { user } = useAuthContext()
// Загружаем заказы поставок
const { data, loading, error } = useQuery(GET_MY_SELLER_CONSUMABLE_SUPPLIES, {
diff --git a/src/components/supplies/supplies-dashboard.tsx b/src/components/supplies/supplies-dashboard.tsx
index 489285a..3e1f642 100644
--- a/src/components/supplies/supplies-dashboard.tsx
+++ b/src/components/supplies/supplies-dashboard.tsx
@@ -10,7 +10,7 @@ import { Alert, AlertDescription } from '@/components/ui/alert'
import { GET_MY_SELLER_GOODS_SUPPLIES } from '@/graphql/mutations/seller-goods-v2'
import { GET_PENDING_SUPPLIES_COUNT } from '@/graphql/queries'
import { GET_MY_SELLER_CONSUMABLE_SUPPLIES } from '@/graphql/queries/seller-consumables-v2'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useRealtime } from '@/hooks/useRealtime'
import { useSidebar } from '@/hooks/useSidebar'
@@ -37,7 +37,7 @@ export function SuppliesDashboard() {
const [activeTab, setActiveTab] = useState('fulfillment')
const [activeSubTab, setActiveSubTab] = useState('goods')
const [activeThirdTab, setActiveThirdTab] = useState('cards')
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [_statisticsData, _setStatisticsData] = useState(null)
// Загружаем счетчик поставок, требующих одобрения
diff --git a/src/components/supplies/wb-product-cards.tsx b/src/components/supplies/wb-product-cards.tsx
index 95d4c9a..22b6ec3 100644
--- a/src/components/supplies/wb-product-cards.tsx
+++ b/src/components/supplies/wb-product-cards.tsx
@@ -32,7 +32,7 @@ import { ProductCardSkeletonGrid } from '@/components/ui/product-card-skeleton'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { CREATE_WILDBERRIES_SUPPLY } from '@/graphql/mutations'
import { GET_MY_COUNTERPARTIES, GET_COUNTERPARTY_SERVICES, GET_COUNTERPARTY_SUPPLIES } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
import { apolloClient } from '@/lib/apollo-client'
import { WildberriesService } from '@/services/wildberries-service'
@@ -62,7 +62,7 @@ export function WBProductCards({
selectedCards: externalSelectedCards,
setSelectedCards: externalSetSelectedCards,
}: WBProductCardsProps) {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { getSidebarMargin } = useSidebar()
const [searchTerm, setSearchTerm] = useState('')
const [loading, setLoading] = useState(false)
diff --git a/src/components/ui/file-uploader.tsx b/src/components/ui/file-uploader.tsx
index 868d09d..92888cc 100644
--- a/src/components/ui/file-uploader.tsx
+++ b/src/components/ui/file-uploader.tsx
@@ -5,7 +5,7 @@ import NextImage from 'next/image'
import { useState, useRef } from 'react'
import { Button } from '@/components/ui/button'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
interface FileUploaderProps {
onSendFile: (
@@ -26,7 +26,7 @@ interface UploadedFile {
}
export function FileUploader({ onSendFile }: FileUploaderProps) {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [isUploading, setIsUploading] = useState(false)
const [selectedFile, setSelectedFile] = useState(null)
const fileInputRef = useRef(null)
diff --git a/src/components/ui/voice-recorder.tsx b/src/components/ui/voice-recorder.tsx
index 77e70c1..fab9c38 100644
--- a/src/components/ui/voice-recorder.tsx
+++ b/src/components/ui/voice-recorder.tsx
@@ -4,14 +4,14 @@ import { Mic, MicOff, Square, Send, Trash2 } from 'lucide-react'
import { useState, useRef, useEffect } from 'react'
import { Button } from '@/components/ui/button'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
interface VoiceRecorderProps {
onSendVoice: (audioUrl: string, duration: number) => void
}
export function VoiceRecorder({ onSendVoice }: VoiceRecorderProps) {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const [isRecording, setIsRecording] = useState(false)
const [recordedAudio, setRecordedAudio] = useState(null)
const [duration, setDuration] = useState(0)
diff --git a/src/components/wb-warehouse/wb-warehouse-dashboard.tsx b/src/components/wb-warehouse/wb-warehouse-dashboard.tsx
index f59d515..95241d1 100644
--- a/src/components/wb-warehouse/wb-warehouse-dashboard.tsx
+++ b/src/components/wb-warehouse/wb-warehouse-dashboard.tsx
@@ -9,7 +9,7 @@ import { Sidebar } from '@/components/dashboard/sidebar'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { SAVE_WB_WAREHOUSE_CACHE } from '@/graphql/mutations'
import { GET_WB_WAREHOUSE_DATA } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
import { useSidebar } from '@/hooks/useSidebar'
import { WildberriesService } from '@/services/wildberries-service'
@@ -48,7 +48,7 @@ interface WBWarehouse {
}
export function WBWarehouseDashboard() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
const { isCollapsed: _isCollapsed, getSidebarMargin } = useSidebar()
const [activeTab, setActiveTab] = useState('fulfillment')
diff --git a/src/contexts/AuthContext.tsx b/src/contexts/AuthContext.tsx
new file mode 100644
index 0000000..77fb446
--- /dev/null
+++ b/src/contexts/AuthContext.tsx
@@ -0,0 +1,643 @@
+'use client'
+
+import { useMutation } from '@apollo/client'
+import { createContext, useContext, useState, useEffect, useCallback, useRef, ReactNode } from 'react'
+
+import {
+ SEND_SMS_CODE,
+ VERIFY_SMS_CODE,
+ REGISTER_FULFILLMENT_ORGANIZATION,
+ REGISTER_SELLER_ORGANIZATION,
+ REGISTER_ORGANIZATION,
+} from '@/graphql/mutations'
+import { GET_ME } from '@/graphql/queries'
+import { useApolloRefresh } from '@/hooks/useApolloRefresh'
+import { setAuthToken, setUserData, removeAuthToken, getAuthToken, apolloClient } from '@/lib/apollo-client'
+
+// Types from useAuth.ts
+interface User {
+ id: string
+ phone: string
+ avatar?: string
+ managerName?: string
+ createdAt?: string
+ organization?: {
+ id: string
+ inn: string
+ kpp?: string
+ name?: string
+ fullName?: string
+ address?: string
+ addressFull?: string
+ ogrn?: string
+ ogrnDate?: string
+ status?: string
+ actualityDate?: string
+ registrationDate?: string
+ liquidationDate?: string
+ managementName?: string
+ managementPost?: string
+ opfCode?: string
+ opfFull?: string
+ opfShort?: string
+ okato?: string
+ oktmo?: string
+ okpo?: string
+ okved?: string
+ employeeCount?: number
+ revenue?: string
+ taxSystem?: string
+ phones?: unknown
+ emails?: unknown
+ type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE'
+ apiKeys: Array<{
+ id: string
+ marketplace: 'WILDBERRIES' | 'OZON'
+ isActive: boolean
+ validationData?: unknown
+ }>
+ }
+}
+
+interface OrganizationRegistrationInput {
+ phone: string
+ type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE'
+
+ // Для бизнес-организаций (FULFILLMENT, LOGIST, WHOLESALE)
+ inn?: string
+ kpp?: string
+ name?: string
+ fullName?: string
+ address?: string
+ addressFull?: string
+ ogrn?: string
+ ogrnDate?: string
+ managerName?: string
+ managerEmail?: string
+ managerPhone?: string
+ description?: string
+ logoUrl?: string
+ website?: string
+ bankName?: string
+ bik?: string
+ correspondentAccount?: string
+ currentAccount?: string
+ taxSystem?: string
+
+ // Для селлеров (SELLER)
+ wbApiKey?: string
+ ozonApiKey?: string
+ ozonClientId?: string
+
+ // Общие поля
+ referralCode?: string | null
+ partnerCode?: string | null
+}
+
+interface AuthContextType {
+ // SMS методы
+ sendSmsCode: (phone: string) => Promise<{ success: boolean; message: string }>
+ verifySmsCode: (
+ phone: string,
+ code: string,
+ ) => Promise<{
+ success: boolean
+ message: string
+ user?: User
+ }>
+
+ // Регистрация организаций
+ registerFulfillmentOrganization: (
+ phone: string,
+ inn: string,
+ type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE',
+ referralCode?: string | null,
+ partnerCode?: string | null,
+ ) => Promise<{
+ success: boolean
+ message: string
+ user?: User
+ }>
+ registerSellerOrganization: (data: {
+ phone: string
+ wbApiKey?: string
+ ozonApiKey?: string
+ ozonClientId?: string
+ referralCode?: string | null
+ partnerCode?: string | null
+ }) => Promise<{
+ success: boolean
+ message: string
+ user?: User
+ }>
+
+ // Универсальная регистрация организации
+ registerOrganization: (input: OrganizationRegistrationInput) => Promise<{
+ success: boolean
+ message: string
+ user?: User
+ }>
+
+ // Состояние
+ user: User | null
+ isAuthenticated: boolean
+ isLoading: boolean
+ checkAuth: () => Promise
+ updateUser: (updatedUser: Partial) => void
+ logout: () => void
+}
+
+const AuthContext = createContext(undefined)
+
+interface AuthProviderProps {
+ children: ReactNode
+}
+
+export function AuthProvider({ children }: AuthProviderProps) {
+ // Состояние аутентификации
+ const [isLoading, setIsLoading] = useState(true) // ИЗМЕНЕНО: Начинаем с true для начальной загрузки
+ const [user, setUser] = useState(null)
+ const [isAuthenticated, setIsAuthenticated] = useState(() => {
+ // Проверяем наличие токена при инициализации
+ return !!getAuthToken()
+ })
+ const [isCheckingAuth, setIsCheckingAuth] = useState(false) // Защита от повторных вызовов
+ const [isInitialized, setIsInitialized] = useState(false) // Новый флаг для отслеживания инициализации
+ const { refreshApolloClient } = useApolloRefresh()
+
+ // GraphQL мутации
+ const [sendSmsCodeMutation] = useMutation(SEND_SMS_CODE)
+ const [verifySmsCodeMutation] = useMutation(VERIFY_SMS_CODE)
+ const [registerFulfillmentMutation] = useMutation(REGISTER_FULFILLMENT_ORGANIZATION)
+ const [registerSellerMutation] = useMutation(REGISTER_SELLER_ORGANIZATION)
+ const [registerOrganizationMutation] = useMutation(REGISTER_ORGANIZATION)
+
+ // Функция для обновления данных пользователя
+ const updateUser = (updatedUser: Partial) => {
+ setUser((currentUser) => {
+ if (!currentUser) return currentUser
+ return { ...currentUser, ...updatedUser }
+ })
+ }
+
+ // Проверка авторизации
+ const checkAuth = useCallback(async () => {
+ if (isCheckingAuth) {
+ console.warn('AuthContext - checkAuth already in progress, skipping')
+ return
+ }
+
+ const token = getAuthToken()
+ console.warn('AuthContext - checkAuth called, token exists:', !!token)
+
+ if (!token) {
+ setIsAuthenticated(false)
+ setUser(null)
+ setIsCheckingAuth(false)
+ return
+ }
+
+ setIsCheckingAuth(true)
+
+ try {
+ console.warn('AuthContext - Making GET_ME query')
+ const { data } = await apolloClient.query({
+ query: GET_ME,
+ errorPolicy: 'all',
+ fetchPolicy: 'network-only', // Всегда делаем свежий запрос
+ })
+
+ console.warn('AuthContext - GET_ME response:', { data, me: data?.me })
+ if (data?.me) {
+ console.warn('AuthContext - Setting user:', data.me)
+ setUser(data.me)
+ setIsAuthenticated(true)
+ setUserData(data.me)
+
+ // Форсируем обновление состояния
+ console.warn('🔄 AuthContext - State updated, user is now:', data.me)
+ } else {
+ console.warn('AuthContext - No user data in response')
+ setIsAuthenticated(false)
+ setUser(null)
+ }
+ } catch (error: unknown) {
+ console.warn('AuthContext - GET_ME error:', error)
+ if (
+ (error as { graphQLErrors?: Array<{ extensions?: { code?: string } }> })?.graphQLErrors?.some(
+ (e) => e.extensions?.code === 'UNAUTHENTICATED',
+ )
+ ) {
+ logout()
+ } else {
+ setIsAuthenticated(false)
+ setUser(null)
+ }
+ } finally {
+ setIsCheckingAuth(false)
+ }
+ }, [])
+
+ // Инициализация контекста - проверяем пользователя при наличии токена
+ useEffect(() => {
+ const initAuth = async () => {
+ const token = getAuthToken()
+
+ console.warn('🔐 AuthContext - Initialization started:', {
+ hasToken: !!token,
+ timestamp: new Date().toISOString()
+ })
+
+ if (token) {
+ // Если есть токен, проверяем пользователя
+ await checkAuth()
+ } else {
+ // Только устанавливаем состояние без токена
+ setIsAuthenticated(false)
+ setUser(null)
+ }
+
+ setIsLoading(false)
+ setIsInitialized(true)
+ console.warn('🔐 AuthContext - Initialization complete:', {
+ isAuthenticated,
+ hasUser: !!user,
+ timestamp: new Date().toISOString()
+ })
+ }
+
+ if (!isInitialized) {
+ initAuth()
+ }
+ }, []) // Только при первом монтировании
+
+ const sendSmsCode = async (phone: string) => {
+ try {
+ setIsLoading(true)
+ const { data } = await sendSmsCodeMutation({
+ variables: { phone },
+ })
+
+ return {
+ success: data.sendSmsCode.success,
+ message: data.sendSmsCode.message,
+ }
+ } catch (error) {
+ console.error('Error sending SMS code:', error)
+ return {
+ success: false,
+ message: 'Ошибка при отправке SMS кода',
+ }
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const verifySmsCode = async (phone: string, code: string) => {
+ try {
+ setIsLoading(true)
+ console.warn('🔑 AuthContext - Starting SMS verification:', { phone, code })
+ console.warn('🔑 AuthContext - BEFORE setState:', {
+ isAuthenticated: isAuthenticated,
+ hasUser: !!user,
+ currentUser: user ? { id: user.id, hasOrganization: !!user.organization } : null,
+ })
+
+ const { data } = await verifySmsCodeMutation({
+ variables: { phone, code },
+ })
+
+ console.warn('🔑 AuthContext - GraphQL response received:', data)
+ const result = data.verifySmsCode
+ console.warn('🔑 AuthContext - SMS verification result:', {
+ success: result.success,
+ hasToken: !!result.token,
+ hasUser: !!result.user,
+ userOrganization: result.user?.organization ? 'exists' : 'null',
+ message: result.message,
+ })
+
+ if (result.success && result.token && result.user) {
+ console.warn('🔑 AuthContext - SUCCESS: Saving token and updating state')
+
+ // Сохраняем токен и данные пользователя
+ console.warn('🔑 AuthContext - Step 1: Saving to localStorage/apollo')
+ setAuthToken(result.token)
+ setUserData(result.user)
+
+ // Обновляем состояние контекста
+ console.warn('🔑 AuthContext - Step 2: Updating React state')
+ setUser(result.user)
+ setIsAuthenticated(true)
+
+ console.warn('🔑 AuthContext - AFTER setState:', {
+ isAuthenticated: true,
+ hasUser: !!result.user,
+ userOrganization: result.user.organization,
+ userId: result.user.id,
+ })
+
+ // Проверяем что токен действительно сохранился
+ const savedToken = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null
+ console.warn('🔑 AuthContext - Step 3: Verification - Token saved to localStorage:', !!savedToken)
+
+ // Принудительно обновляем Apollo Client
+ console.warn('🔑 AuthContext - Step 4: Refreshing Apollo Client')
+ await refreshApolloClient() // ДОБАВЛЕН await
+
+ // Добавляем небольшую задержку для гарантии обновления React состояния
+ console.warn('🔑 AuthContext - Step 5: Waiting for state propagation')
+ await new Promise(resolve => setTimeout(resolve, 50))
+
+ console.warn('🔑 AuthContext - SUCCESS COMPLETE: All steps finished, returning user data')
+ console.warn('🔑 AuthContext - Final state check:', {
+ willReturnUser: !!result.user,
+ userHasOrganization: !!result.user.organization,
+ timestamp: new Date().toISOString(),
+ })
+
+ return {
+ success: true,
+ message: result.message,
+ user: result.user,
+ }
+ }
+
+ console.warn('🔑 AuthContext - FAILURE: SMS verification failed or missing data')
+ return {
+ success: false,
+ message: result.message,
+ }
+ } catch (error) {
+ console.error('🔑 AuthContext - ERROR in verifySmsCode:', error)
+ return {
+ success: false,
+ message: 'Ошибка при проверке SMS кода',
+ }
+ } finally {
+ console.warn('🔑 AuthContext - Cleaning up: setIsLoading(false)')
+ setIsLoading(false)
+ }
+ }
+
+ const registerFulfillmentOrganization = async (
+ phone: string,
+ inn: string,
+ type: 'FULFILLMENT' | 'LOGIST' | 'WHOLESALE',
+ referralCode?: string | null,
+ partnerCode?: string | null,
+ ) => {
+ console.warn('🎬 AuthContext - registerFulfillmentOrganization вызван с параметрами:', {
+ phone, inn, type, referralCode, partnerCode,
+ })
+
+ // Rollback checkpoint
+ const rollbackState = {
+ previousUser: user,
+ previousIsAuthenticated: isAuthenticated,
+ previousToken: typeof window !== 'undefined' ? localStorage.getItem('authToken') : null,
+ }
+
+ try {
+ setIsLoading(true)
+
+ const { data } = await registerFulfillmentMutation({
+ variables: {
+ input: { phone, inn, type, referralCode, partnerCode },
+ },
+ })
+
+ const result = data.registerFulfillmentOrganization
+
+ if (result.success && result.user) {
+ // Обновляем данные пользователя
+ setUserData(result.user)
+ setUser(result.user)
+ console.warn('✅ AuthContext - registerFulfillmentOrganization успешно завершена')
+
+ return {
+ success: true,
+ message: result.message,
+ user: result.user,
+ }
+ }
+
+ return {
+ success: false,
+ message: result.message,
+ }
+ } catch (error) {
+ console.error('❌ AuthContext - Error registering fulfillment organization:', error)
+
+ // Rollback on error
+ try {
+ if (rollbackState.previousToken) {
+ setAuthToken(rollbackState.previousToken)
+ }
+
+ if (rollbackState.previousUser) {
+ setUser(rollbackState.previousUser)
+ setUserData(rollbackState.previousUser)
+ }
+
+ setIsAuthenticated(rollbackState.previousIsAuthenticated)
+ refreshApolloClient()
+ } catch (rollbackError) {
+ console.error('🚨 AuthContext - Critical error during rollback:', rollbackError)
+ }
+
+ return {
+ success: false,
+ message: 'Ошибка при регистрации фулфилмент организации',
+ }
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const registerSellerOrganization = async (data: {
+ phone: string
+ wbApiKey?: string
+ ozonApiKey?: string
+ ozonClientId?: string
+ referralCode?: string | null
+ partnerCode?: string | null
+ }) => {
+ console.warn('🛍️ AuthContext - registerSellerOrganization вызван')
+
+ // Rollback checkpoint
+ const rollbackState = {
+ previousUser: user,
+ previousIsAuthenticated: isAuthenticated,
+ previousToken: typeof window !== 'undefined' ? localStorage.getItem('authToken') : null,
+ }
+
+ try {
+ setIsLoading(true)
+
+ const { data: result } = await registerSellerMutation({
+ variables: { input: data },
+ })
+
+ const registerResult = result.registerSellerOrganization
+
+ if (registerResult.success && registerResult.user) {
+ // Обновляем данные пользователя
+ setUserData(registerResult.user)
+ setUser(registerResult.user)
+
+ return {
+ success: true,
+ message: registerResult.message,
+ user: registerResult.user,
+ }
+ }
+
+ return {
+ success: false,
+ message: registerResult.message,
+ }
+ } catch (error) {
+ console.error('❌ AuthContext - Error registering seller organization:', error)
+
+ // Rollback on error
+ try {
+ if (rollbackState.previousToken) {
+ setAuthToken(rollbackState.previousToken)
+ }
+
+ if (rollbackState.previousUser) {
+ setUser(rollbackState.previousUser)
+ setUserData(rollbackState.previousUser)
+ }
+
+ setIsAuthenticated(rollbackState.previousIsAuthenticated)
+ refreshApolloClient()
+ } catch (rollbackError) {
+ console.error('🚨 AuthContext - Critical error during seller rollback:', rollbackError)
+ }
+
+ return {
+ success: false,
+ message: 'Ошибка при регистрации селлер организации',
+ }
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ // Универсальная функция регистрации организации
+ const registerOrganization = async (input: OrganizationRegistrationInput) => {
+ console.warn('🚀 AuthContext - registerOrganization (NEW) вызван:', input.type)
+
+ // Rollback checkpoint
+ const rollbackState = {
+ previousUser: user,
+ previousIsAuthenticated: isAuthenticated,
+ previousToken: typeof window !== 'undefined' ? localStorage.getItem('authToken') : null,
+ }
+
+ try {
+ setIsLoading(true)
+
+ const { data } = await registerOrganizationMutation({
+ variables: { input },
+ })
+
+ const result = data.registerOrganization
+
+ if (result.success && result.user && result.token) {
+ // Обновляем данные пользователя и токен
+ setAuthToken(result.token)
+ setUserData(result.user)
+ setUser(result.user)
+ setIsAuthenticated(true)
+
+ // Принудительно обновляем Apollo Client
+ refreshApolloClient()
+
+ return {
+ success: true,
+ message: result.message,
+ user: result.user,
+ }
+ }
+
+ return {
+ success: false,
+ message: result.message,
+ }
+ } catch (error) {
+ console.error('❌ AuthContext - Error in registerOrganization (NEW):', error)
+
+ // Rollback on error
+ try {
+ if (rollbackState.previousToken) {
+ setAuthToken(rollbackState.previousToken)
+ }
+
+ if (rollbackState.previousUser) {
+ setUser(rollbackState.previousUser)
+ setUserData(rollbackState.previousUser)
+ }
+
+ setIsAuthenticated(rollbackState.previousIsAuthenticated)
+ refreshApolloClient()
+ } catch (rollbackError) {
+ console.error('🚨 AuthContext - Critical error during registerOrganization rollback:', rollbackError)
+ }
+
+ return {
+ success: false,
+ message: 'Ошибка при регистрации организации',
+ }
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ const logout = () => {
+ console.warn('AuthContext - Logging out')
+ removeAuthToken()
+ setUser(null)
+ setIsAuthenticated(false)
+ refreshApolloClient()
+
+ // Делаем redirect только на клиенте, асинхронно для избежания SSR проблем
+ setTimeout(() => {
+ if (typeof window !== 'undefined') {
+ window.location.href = '/'
+ }
+ }, 0)
+ }
+
+ const value: AuthContextType = {
+ // SMS методы
+ sendSmsCode,
+ verifySmsCode,
+
+ // Регистрация организаций
+ registerFulfillmentOrganization,
+ registerSellerOrganization,
+ registerOrganization,
+
+ // Состояние
+ user,
+ isAuthenticated,
+ isLoading,
+ checkAuth,
+ updateUser,
+ logout,
+ }
+
+ return {children}
+}
+
+// Hook для использования AuthContext
+export function useAuthContext() {
+ const context = useContext(AuthContext)
+ if (context === undefined) {
+ throw new Error('useAuthContext must be used within an AuthProvider')
+ }
+ return context
+}
\ No newline at end of file
diff --git a/src/graphql/resolvers/domains/organization-management.ts b/src/graphql/resolvers/domains/organization-management.ts
index 81236c7..1f1bc9c 100644
--- a/src/graphql/resolvers/domains/organization-management.ts
+++ b/src/graphql/resolvers/domains/organization-management.ts
@@ -263,7 +263,7 @@ export const organizationManagementResolvers: DomainResolvers = {
console.warn('🔄 Начинаем транзакцию создания фулфилмент организации:', {
inn: args.input.inn,
type: args.input.type,
- phone: args.input.phone
+ phone: args.input.phone,
})
// Создаем организацию
@@ -307,7 +307,7 @@ export const organizationManagementResolvers: DomainResolvers = {
console.warn('✅ Организация создана в транзакции:', {
organizationId: organization.id,
- name: organization.name
+ name: organization.name,
})
// Создаем или обновляем пользователя в той же транзакции
@@ -326,7 +326,7 @@ export const organizationManagementResolvers: DomainResolvers = {
})
console.warn('✅ Существующий пользователь обновлен в транзакции:', {
userId: user.id,
- organizationId: organization.id
+ organizationId: organization.id,
})
} else {
user = await tx.user.create({
@@ -344,21 +344,21 @@ export const organizationManagementResolvers: DomainResolvers = {
})
console.warn('✅ Новый пользователь создан в транзакции:', {
userId: user.id,
- organizationId: organization.id
+ organizationId: organization.id,
})
}
return { organization, user }
}, {
timeout: 30000, // 30 секунд максимум на транзакцию
- maxWait: 5000 // Максимум 5 секунд ожидания блокировки
+ maxWait: 5000, // Максимум 5 секунд ожидания блокировки
})
const { organization, user } = result
console.warn('🎉 Транзакция успешно завершена:', {
organizationId: organization.id,
userId: user.id,
- organizationType: organization.type
+ organizationType: organization.type,
})
// 🤝 ОБРАБОТКА ПАРТНЕРСКОГО КОДА (автопартнерство) В ОТДЕЛЬНОЙ ТРАНЗАКЦИИ
@@ -368,7 +368,7 @@ export const organizationManagementResolvers: DomainResolvers = {
partnerCode: args.input.partnerCode,
hasPartnerCode: !!args.input.partnerCode,
partnerCodeLength: args.input.partnerCode?.length,
- organizationId: organization.id
+ organizationId: organization.id,
})
// Находим партнера по партнерскому коду
@@ -389,7 +389,7 @@ export const organizationManagementResolvers: DomainResolvers = {
console.warn('🔄 Начинаем транзакцию партнерства:', {
partnerId: partner.id,
organizationId: organization.id,
- points: 100
+ points: 100,
})
// Создаем реферальную транзакцию (100 сфер)
@@ -437,7 +437,7 @@ export const organizationManagementResolvers: DomainResolvers = {
console.warn('✅ Партнерская транзакция завершена успешно')
}, {
timeout: 20000, // 20 секунд для партнерских операций
- maxWait: 3000 // 3 секунды ожидания
+ maxWait: 3000, // 3 секунды ожидания
})
console.warn('🤝 Автоматическое партнерство создано по partnerCode:', {
@@ -568,7 +568,7 @@ export const organizationManagementResolvers: DomainResolvers = {
const result = await prisma.$transaction(async (tx) => {
console.warn('🔄 Начинаем транзакцию создания селлер организации:', {
phone: args.input.phone,
- type: 'SELLER'
+ type: 'SELLER',
})
// Создаем организацию селлера с псевдо-ИНН
@@ -587,7 +587,7 @@ export const organizationManagementResolvers: DomainResolvers = {
console.warn('✅ Селлер организация создана в транзакции:', {
organizationId: organization.id,
- name: organization.name
+ name: organization.name,
})
// Создаем или обновляем пользователя в той же транзакции
@@ -606,7 +606,7 @@ export const organizationManagementResolvers: DomainResolvers = {
})
console.warn('✅ Существующий пользователь обновлен в транзакции:', {
userId: user.id,
- organizationId: organization.id
+ organizationId: organization.id,
})
} else {
user = await tx.user.create({
@@ -624,21 +624,21 @@ export const organizationManagementResolvers: DomainResolvers = {
})
console.warn('✅ Новый пользователь создан в транзакции:', {
userId: user.id,
- organizationId: organization.id
+ organizationId: organization.id,
})
}
return { organization, user }
}, {
timeout: 30000, // 30 секунд максимум на транзакцию
- maxWait: 5000 // Максимум 5 секунд ожидания блокировки
+ maxWait: 5000, // Максимум 5 секунд ожидания блокировки
})
const { organization, user } = result
console.warn('🎉 Транзакция селлера успешно завершена:', {
organizationId: organization.id,
userId: user.id,
- organizationType: organization.type
+ organizationType: organization.type,
})
// 🤝 ОБРАБОТКА ПАРТНЕРСКОГО КОДА ДЛЯ СЕЛЛЕРА (автопартнерство) В ОТДЕЛЬНОЙ ТРАНЗАКЦИИ
@@ -648,7 +648,7 @@ export const organizationManagementResolvers: DomainResolvers = {
partnerCode: args.input.partnerCode,
hasPartnerCode: !!args.input.partnerCode,
partnerCodeLength: args.input.partnerCode?.length,
- organizationId: organization.id
+ organizationId: organization.id,
})
// Находим партнера по партнерскому коду
@@ -669,7 +669,7 @@ export const organizationManagementResolvers: DomainResolvers = {
console.warn('🔄 Начинаем транзакцию партнерства селлера:', {
partnerId: partner.id,
organizationId: organization.id,
- points: 100
+ points: 100,
})
// Создаем реферальную транзакцию (100 сфер)
@@ -717,7 +717,7 @@ export const organizationManagementResolvers: DomainResolvers = {
console.warn('✅ Партнерская транзакция селлера завершена успешно')
}, {
timeout: 20000, // 20 секунд для партнерских операций
- maxWait: 3000 // 3 секунды ожидания
+ maxWait: 3000, // 3 секунды ожидания
})
console.warn('🤝 Автоматическое партнерство селлера создано по partnerCode:', {
diff --git a/src/graphql/resolvers/domains/user-management.ts b/src/graphql/resolvers/domains/user-management.ts
index daceb59..344afbe 100644
--- a/src/graphql/resolvers/domains/user-management.ts
+++ b/src/graphql/resolvers/domains/user-management.ts
@@ -51,7 +51,7 @@ export const userManagementResolvers: DomainResolvers = {
found: !!user,
hasOrganization: !!user?.organization,
organizationType: user?.organization?.type,
- phone: user?.phone
+ phone: user?.phone,
})
return user
diff --git a/src/graphql/resolvers/domains/wildberries.ts b/src/graphql/resolvers/domains/wildberries.ts
index 8631247..050955e 100644
--- a/src/graphql/resolvers/domains/wildberries.ts
+++ b/src/graphql/resolvers/domains/wildberries.ts
@@ -204,9 +204,22 @@ export const wildberriesResolvers: DomainResolvers = {
type: typeof statistics,
isArray: Array.isArray(statistics),
length: statistics?.length,
- firstItem: statistics?.[0] ? Object.keys(statistics[0]) : null
+ firstItem: statistics?.[0] ? Object.keys(statistics[0]) : null,
})
+ // Сохраняем успешные данные в кеш для будущего использования при rate limit
+ if (statistics && statistics.length > 0) {
+ const cacheKey = `wb_stats_${user.organization.id}_${startDate}_${endDate}`
+ try {
+ // Пока используем простое логирование, позже можно добавить Redis или таблицу кеша
+ console.log('💾 CACHING SUCCESS DATA:', { cacheKey, dataLength: statistics.length })
+ // TODO: Сохранить в Redis или БД
+ // await redis.setex(cacheKey, 3600, JSON.stringify(statistics)) // 1 час TTL
+ } catch (cacheError) {
+ console.error('❌ Failed to cache statistics:', cacheError)
+ }
+ }
+
console.log('✅ GET_WILDBERRIES_STATISTICS DOMAIN SUCCESS')
return {
@@ -216,6 +229,27 @@ export const wildberriesResolvers: DomainResolvers = {
}
} catch (error: any) {
console.error('❌ GET_WILDBERRIES_STATISTICS DOMAIN ERROR:', error)
+
+ // Если ошибка rate limit (429), пытаемся получить последние кешированные данные
+ if (error.message && error.message.includes('429')) {
+ console.warn('🔄 RATE LIMIT: Checking for cached statistics data')
+
+ // Ищем последние кешированные данные для этой организации и периода
+ const cacheKey = `wb_stats_${user?.organization?.id}_${startDate}_${endDate}`
+
+ try {
+ // Проверяем кеш в БД (можно использовать отдельную таблицу или Redis)
+ // Пока что возвращаем информативное сообщение
+ return {
+ success: false,
+ message: 'Достигнут лимит запросов WB API. Подождите и попробуйте снова через несколько минут.',
+ data: [],
+ }
+ } catch (cacheError) {
+ console.error('❌ Cache lookup failed:', cacheError)
+ }
+ }
+
return {
success: false,
message: error.message || 'Ошибка при получении статистики',
diff --git a/src/graphql/resolvers/index.ts b/src/graphql/resolvers/index.ts
index 8922fb6..5d9f9f6 100644
--- a/src/graphql/resolvers/index.ts
+++ b/src/graphql/resolvers/index.ts
@@ -16,14 +16,14 @@ import { logisticsConsumablesResolvers as logisticsConsumablesDomainResolvers }
import { messagingResolvers } from './domains/messaging'
import { organizationManagementResolvers } from './domains/organization-management'
import { productsResolvers } from './domains/products'
+import { referralResolvers as referralDomainResolvers } from './domains/referrals'
+import { sellerConsumablesResolvers } from './domains/seller-consumables'
+import { sellerGoodsResolvers as sellerGoodsDomainResolvers } from './domains/seller-goods'
+import { servicesResolvers as servicesDomainResolvers } from './domains/services'
+import { suppliesResolvers } from './domains/supplies'
import { supplyOrdersResolvers } from './domains/supply-orders'
import { userManagementResolvers } from './domains/user-management'
-import { suppliesResolvers } from './domains/supplies'
-import { referralResolvers as referralDomainResolvers } from './domains/referrals'
-import { servicesResolvers as servicesDomainResolvers } from './domains/services'
-import { sellerGoodsResolvers as sellerGoodsDomainResolvers } from './domains/seller-goods'
import { wildberriesResolvers } from './domains/wildberries'
-import { sellerConsumablesResolvers } from './domains/seller-consumables'
// V2 импорты удалены - заменены на доменные резолверы
// import { integrateSecurityWithExistingResolvers } from './secure-integration' // ВРЕМЕННО ОТКЛЮЧЕНО - экспорты не найдены
// import { secureSuppliesResolvers } from './secure-supplies' // ВРЕМЕННО ОТКЛЮЧЕНО - экспорты не найдены
diff --git a/src/hooks/useRoleGuard.ts b/src/hooks/useRoleGuard.ts
index 78d589a..a0994f8 100644
--- a/src/hooks/useRoleGuard.ts
+++ b/src/hooks/useRoleGuard.ts
@@ -3,7 +3,7 @@
import { redirect } from 'next/navigation'
import { useEffect } from 'react'
-import { useAuth } from '@/hooks/useAuth'
+import { useAuthContext } from '@/contexts/AuthContext'
type UserRole = 'SELLER' | 'FULFILLMENT' | 'WHOLESALE' | 'LOGIST'
@@ -22,7 +22,7 @@ type UserRole = 'SELLER' | 'FULFILLMENT' | 'WHOLESALE' | 'LOGIST'
* ```
*/
export function useRoleGuard(requiredRole: UserRole) {
- const { user } = useAuth()
+ const { user } = useAuthContext()
useEffect(() => {
// Ждем загрузки данных пользователя
diff --git a/src/hooks/useWBWarehouseData.ts b/src/hooks/useWBWarehouseData.ts
index bae5702..e079e52 100644
--- a/src/hooks/useWBWarehouseData.ts
+++ b/src/hooks/useWBWarehouseData.ts
@@ -4,9 +4,9 @@ import { useMutation, useQuery } from '@apollo/client'
import { useEffect, useState } from 'react'
import { toast } from 'sonner'
+import { useAuthContext } from '@/contexts/AuthContext'
import { SAVE_WB_WAREHOUSE_CACHE } from '@/graphql/mutations'
import { GET_WB_WAREHOUSE_DATA } from '@/graphql/queries'
-import { useAuth } from '@/hooks/useAuth'
import { WildberriesService } from '@/services/wildberries-service'
interface WBStock {
@@ -40,7 +40,7 @@ interface WBWarehouse {
}
export function useWBWarehouseData() {
- const { user } = useAuth()
+ const { user } = useAuthContext()
// Состояние данных WB Warehouse
const [stocks, setStocks] = useState([])
diff --git a/src/services/wildberries-service.ts b/src/services/wildberries-service.ts
index 430c798..618674d 100644
--- a/src/services/wildberries-service.ts
+++ b/src/services/wildberries-service.ts
@@ -399,7 +399,7 @@ class WildberriesService {
url,
method: options.method || 'GET',
hasAuth: !!authHeader.Authorization,
- authType: url.includes('marketplace-api') || url.includes('content-api') ? 'Bearer' : 'Direct'
+ authType: url.includes('marketplace-api') || url.includes('content-api') ? 'Bearer' : 'Direct',
})
// Добавляем AbortController для timeout
@@ -424,7 +424,7 @@ class WildberriesService {
url,
status: response.status,
statusText: response.statusText,
- headers: Object.fromEntries(Object.entries(authHeader))
+ headers: Object.fromEntries(Object.entries(authHeader)),
})
throw new Error(`WB API Error: ${response.status} ${response.statusText}`)
}