Files
sfera-new/src/components/fulfillment-supplies/fulfillment-supplies-layout.tsx
Veronika Smirnova 0e3ffc179c feat(fulfillment-supplies): миграция формы создания поставок расходников на v2 систему
- Обновлена форма создания поставок расходников фулфилмента для использования v2 GraphQL API
- Заменена мутация CREATE_SUPPLY_ORDER на CREATE_FULFILLMENT_CONSUMABLE_SUPPLY
- Обновлена структура input данных под новый формат v2
- Сделано поле логистики опциональным
- Добавлено поле notes для комментариев к поставке
- Обновлены refetchQueries на новые v2 запросы
- Исправлены TypeScript ошибки в интерфейсах
- Удалена дублирующая страница consumables-v2
- Сохранен оригинальный богатый UI интерфейс формы (819 строк)
- Подтверждена работа с новой таблицей FulfillmentConsumableSupplyOrder

Технические изменения:
- src/components/fulfillment-supplies/create-fulfillment-consumables-supply-v2.tsx - основная форма
- src/components/fulfillment-supplies/fulfillment-supplies-layout.tsx - обновлена навигация
- Добавлены недостающие поля quantity и ordered в интерфейсы продуктов
- Исправлены импорты и зависимости

Результат: форма полностью интегрирована с v2 системой поставок, которая использует отдельные таблицы для каждого типа поставок согласно новой архитектуре.

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-25 07:52:46 +03:00

309 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import { useQuery } from '@apollo/client'
import { Building2, ShoppingCart, Package, Wrench, RotateCcw, Clock, FileText, CheckCircle, ChevronRight, Home } from 'lucide-react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import React from 'react'
import { Sidebar } from '@/components/dashboard/sidebar'
import { GET_PENDING_SUPPLIES_COUNT } from '@/graphql/queries'
import { useRealtime } from '@/hooks/useRealtime'
import { useSidebar } from '@/hooks/useSidebar'
// Компонент для отображения бейджа с уведомлениями
function NotificationBadge({ count }: { count: number }) {
if (count === 0) return null
return (
<div className="ml-1 bg-red-500 text-white text-xs font-bold rounded-full min-w-[16px] h-4 flex items-center justify-center px-1">
{count > 99 ? '99+' : count}
</div>
)
}
// Breadcrumbs компонент
function Breadcrumbs({ pathname }: { pathname: string }) {
const getBreadcrumbs = (path: string) => {
const segments = path.split('/').filter(Boolean)
const breadcrumbs = [
{ name: 'Главная', href: '/dashboard', icon: Home }
]
if (segments[0] === 'fulfillment-supplies') {
breadcrumbs.push({ name: 'Входящие поставки', href: '/fulfillment-supplies', icon: Building2 })
if (segments[1]) {
const categoryMap: Record<string, string> = {
'goods': 'Товары',
'detailed-supplies': 'Расходники фулфилмента',
'consumables': 'Расходники селлеров',
'returns': 'Возвраты с ПВЗ'
}
const category = categoryMap[segments[1]]
if (category) {
breadcrumbs.push({
name: category,
href: segments[2] ? `/fulfillment-supplies/${segments[1]}` : path,
icon: Package
})
if (segments[1] === 'goods' && segments[2]) {
const subcategoryMap: Record<string, string> = {
'new': 'Новые',
'receiving': 'Приёмка',
'received': 'Принято'
}
const subcategory = subcategoryMap[segments[2]]
if (subcategory) {
breadcrumbs.push({ name: subcategory, href: path, icon: Clock })
}
}
}
}
}
return breadcrumbs
}
const breadcrumbs = getBreadcrumbs(pathname)
return (
<nav className="flex items-center space-x-2 text-sm text-white/60 mb-4">
{breadcrumbs.map((breadcrumb, index) => (
<React.Fragment key={breadcrumb.href}>
{index > 0 && <ChevronRight className="h-4 w-4" />}
{index === breadcrumbs.length - 1 ? (
<span className="text-white font-medium flex items-center gap-1">
{breadcrumb.icon && <breadcrumb.icon className="h-4 w-4" />}
{breadcrumb.name}
</span>
) : (
<Link
href={breadcrumb.href}
className="hover:text-white transition-colors flex items-center gap-1"
>
{breadcrumb.icon && <breadcrumb.icon className="h-4 w-4" />}
{breadcrumb.name}
</Link>
)}
</React.Fragment>
))}
</nav>
)
}
export function FulfillmentSuppliesLayout({ children }: { children: React.ReactNode }) {
const { getSidebarMargin } = useSidebar()
const pathname = usePathname()
// Загружаем данные о непринятых поставках
const { data: pendingData, error: pendingError, refetch: refetchPending } = useQuery(GET_PENDING_SUPPLIES_COUNT, {
fetchPolicy: 'cache-first',
errorPolicy: 'ignore',
onError: (error) => {
console.error('❌ GET_PENDING_SUPPLIES_COUNT Error:', error)
},
})
// Realtime: обновление бейджа
useRealtime({
onEvent: (evt) => {
if (evt.type === 'supply-order:new' || evt.type === 'supply-order:updated') {
refetchPending()
}
},
})
// Логируем ошибку для диагностики
React.useEffect(() => {
if (pendingError) {
console.error('🚨 Ошибка загрузки счетчиков поставок:', pendingError)
}
}, [pendingError])
// Для фулфилмента считаем только поставки, НЕ заявки на партнерство
const pendingCount = pendingData?.pendingSuppliesCount?.supplyOrders || 0
const ourSupplyOrdersCount = pendingData?.pendingSuppliesCount?.ourSupplyOrders || 0
const sellerSupplyOrdersCount = pendingData?.pendingSuppliesCount?.sellerSupplyOrders || 0
// Определяем активные табы на основе pathname
const getActiveTab = () => {
if (pathname.startsWith('/fulfillment-supplies/goods')) return 'fulfillment'
if (pathname.includes('/detailed-supplies')) return 'fulfillment'
if (pathname.includes('/consumables')) return 'fulfillment'
if (pathname.includes('/returns')) return 'fulfillment'
return 'fulfillment'
}
const getActiveSubTab = () => {
if (pathname.startsWith('/fulfillment-supplies/goods')) return 'goods'
if (pathname.includes('/detailed-supplies')) return 'detailed-supplies'
if (pathname.includes('/consumables')) return 'consumables'
if (pathname.includes('/returns')) return 'returns'
return 'goods'
}
const getActiveThirdTab = () => {
if (pathname.includes('/goods/new')) return 'new'
if (pathname.includes('/goods/receiving')) return 'receiving'
if (pathname.includes('/goods/received')) return 'received'
return 'new'
}
const activeTab = getActiveTab()
const activeSubTab = getActiveSubTab()
const activeThirdTab = getActiveThirdTab()
return (
<div className="h-screen flex overflow-hidden">
<Sidebar />
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
<div className="h-full w-full flex flex-col space-y-4">
{/* Breadcrumbs */}
<Breadcrumbs pathname={pathname} />
{/* БЛОК 1: ТАБЫ ВСЕХ УРОВНЕЙ */}
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6">
{/* УРОВЕНЬ 1: Главные табы */}
<div className="mb-4">
<div className="grid w-full grid-cols-2 bg-white/15 backdrop-blur border-white/30 rounded-xl h-11 p-2">
<Link
href="/fulfillment-supplies/goods/new"
className={`flex items-center gap-2 text-sm font-semibold transition-all duration-200 rounded-lg px-3 ${
activeTab === 'fulfillment'
? 'bg-gradient-to-r from-purple-500/40 to-pink-500/40 text-white shadow-lg'
: 'text-white/80 hover:text-white'
}`}
>
<Building2 className="h-4 w-4" />
<span className="hidden sm:inline">Поставки на фулфилмент</span>
<span className="sm:hidden">Фулфилмент</span>
<NotificationBadge count={pendingCount} />
</Link>
<button
className="flex items-center gap-2 text-sm font-semibold transition-all duration-200 rounded-lg px-3 text-white/40 cursor-not-allowed"
disabled
>
<ShoppingCart className="h-4 w-4" />
<span className="hidden sm:inline">Поставки на маркетплейсы</span>
<span className="sm:hidden">Маркетплейсы</span>
</button>
</div>
</div>
{/* УРОВЕНЬ 2: Подтабы */}
{activeTab === 'fulfillment' && (
<div className="ml-4 mb-3">
<div className="grid w-full grid-cols-4 bg-white/8 backdrop-blur border-white/20 h-9 rounded-lg p-1">
<Link
href="/fulfillment-supplies/goods/new"
className={`flex items-center gap-1 text-xs font-medium transition-all duration-150 rounded-md px-2 ${
activeSubTab === 'goods'
? 'bg-white/15 text-white border-white/20'
: 'text-white/60 hover:text-white/80'
}`}
>
<Package className="h-3 w-3" />
<span className="hidden sm:inline">Товар</span>
<span className="sm:hidden">Т</span>
</Link>
<Link
href="/fulfillment-supplies/detailed-supplies"
className={`flex items-center gap-1 text-xs font-medium transition-all duration-150 rounded-md px-2 relative ${
activeSubTab === 'detailed-supplies'
? 'bg-white/15 text-white border-white/20'
: 'text-white/60 hover:text-white/80'
}`}
>
<Building2 className="h-3 w-3" />
<span className="hidden md:inline">Расходники фулфилмента</span>
<span className="md:hidden hidden sm:inline">Фулфилмент</span>
<span className="sm:hidden">Ф</span>
<NotificationBadge count={ourSupplyOrdersCount} />
</Link>
<Link
href="/fulfillment-supplies/consumables"
className={`flex items-center gap-1 text-xs font-medium transition-all duration-150 rounded-md px-2 relative ${
activeSubTab === 'consumables'
? 'bg-white/15 text-white border-white/20'
: 'text-white/60 hover:text-white/80'
}`}
>
<Wrench className="h-3 w-3" />
<span className="hidden md:inline">Расходники селлеров</span>
<span className="md:hidden hidden sm:inline">Селлеры</span>
<span className="sm:hidden">С</span>
<NotificationBadge count={sellerSupplyOrdersCount} />
</Link>
<Link
href="/fulfillment-supplies/returns"
className={`flex items-center gap-1 text-xs font-medium transition-all duration-150 rounded-md px-2 ${
activeSubTab === 'returns'
? 'bg-white/15 text-white border-white/20'
: 'text-white/60 hover:text-white/80'
}`}
>
<RotateCcw className="h-3 w-3" />
<span className="hidden sm:inline">Возвраты с ПВЗ</span>
<span className="sm:hidden">В</span>
</Link>
</div>
</div>
)}
{/* УРОВЕНЬ 3: Подподтабы для товаров */}
{activeTab === 'fulfillment' && activeSubTab === 'goods' && (
<div className="ml-8">
<div className="grid w-full grid-cols-3 bg-white/5 backdrop-blur border-white/15 h-8 rounded-md p-1">
<Link
href="/fulfillment-supplies/goods/new"
className={`flex items-center gap-1 text-xs font-normal transition-all duration-150 rounded-sm px-2 ${
activeThirdTab === 'new' ? 'bg-white/10 text-white' : 'text-white/50 hover:text-white/70'
}`}
>
<Clock className="h-2.5 w-2.5" />
<span className="hidden sm:inline">Новые</span>
<span className="sm:hidden">Н</span>
</Link>
<Link
href="/fulfillment-supplies/goods/receiving"
className={`flex items-center gap-1 text-xs font-normal transition-all duration-150 rounded-sm px-2 ${
activeThirdTab === 'receiving' ? 'bg-white/10 text-white' : 'text-white/50 hover:text-white/70'
}`}
>
<FileText className="h-2.5 w-2.5" />
<span className="hidden sm:inline">Приёмка</span>
<span className="sm:hidden">П</span>
</Link>
<Link
href="/fulfillment-supplies/goods/received"
className={`flex items-center gap-1 text-xs font-normal transition-all duration-150 rounded-sm px-2 ${
activeThirdTab === 'received' ? 'bg-white/10 text-white' : 'text-white/50 hover:text-white/70'
}`}
>
<CheckCircle className="h-2.5 w-2.5" />
<span className="hidden sm:inline">Принято</span>
<span className="sm:hidden">Пр</span>
</Link>
</div>
</div>
)}
</div>
{/* БЛОК 2: ОСНОВНОЙ КОНТЕНТ */}
<div className="flex-1 overflow-hidden">
<div className="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl h-full overflow-hidden p-6">
<div className="h-full">
{children}
</div>
</div>
</div>
</div>
</main>
</div>
)
}