'use client' import { useQuery } from '@apollo/client' import { Package, Wrench, AlertTriangle, CheckCircle, Clock } from 'lucide-react' import React, { useState, useMemo, useCallback } from 'react' import { toast } from 'sonner' import { Sidebar } from '@/components/dashboard/sidebar' import { GET_MY_FULFILLMENT_SUPPLIES } from '@/graphql/queries' import { useSidebar } from '@/hooks/useSidebar' // Новые компоненты import { SuppliesGrid } from './supplies-grid' import { SuppliesHeader } from './supplies-header' import { SuppliesList } from './supplies-list' import { SuppliesStats } from './supplies-stats' // Типы import { Supply, FilterState, SortState, ViewMode, GroupBy, StatusConfig } from './types' // Статусы расходников с цветами const STATUS_CONFIG = { 'in-stock': { label: 'Доступен', color: 'bg-green-500/20 text-green-300', icon: CheckCircle, }, 'in-transit': { label: 'В пути', color: 'bg-blue-500/20 text-blue-300', icon: Clock, }, confirmed: { label: 'Подтверждено', color: 'bg-cyan-500/20 text-cyan-300', icon: CheckCircle, }, planned: { label: 'Запланировано', color: 'bg-yellow-500/20 text-yellow-300', icon: Clock, }, // Обратная совместимость и специальные статусы available: { label: 'Доступен', color: 'bg-green-500/20 text-green-300', icon: CheckCircle, }, 'low-stock': { label: 'Мало на складе', color: 'bg-yellow-500/20 text-yellow-300', icon: AlertTriangle, }, 'out-of-stock': { label: 'Нет в наличии', color: 'bg-red-500/20 text-red-300', icon: AlertTriangle, }, reserved: { label: 'Зарезервирован', color: 'bg-purple-500/20 text-purple-300', icon: Package, }, } as const export function FulfillmentSuppliesPage() { const { getSidebarMargin } = useSidebar() // Состояния const [viewMode, setViewMode] = useState('grid') const [filters, setFilters] = useState({ search: '', category: '', status: '', supplier: '', lowStock: false, }) const [sort, setSort] = useState({ field: 'name', direction: 'asc', }) const [showFilters, setShowFilters] = useState(false) const [groupBy, setGroupBy] = useState('none') const [expandedSupplies, setExpandedSupplies] = useState>(new Set()) // Загрузка данных const { data: suppliesData, loading, error, refetch, } = useQuery(GET_MY_FULFILLMENT_SUPPLIES, { fetchPolicy: 'cache-and-network', onError: (error) => { toast.error('Ошибка загрузки расходников фулфилмента: ' + error.message) }, }) const supplies: Supply[] = suppliesData?.myFulfillmentSupplies || [] // Логирование для отладки console.warn('🔥🔥🔥 FULFILLMENT SUPPLIES PAGE DATA 🔥🔥🔥', { suppliesCount: supplies.length, supplies: supplies.map((s) => ({ id: s.id, name: s.name, status: s.status, currentStock: s.currentStock, quantity: s.quantity, })), }) // Функции const getStatusConfig = useCallback((status: string): StatusConfig => { return STATUS_CONFIG[status as keyof typeof STATUS_CONFIG] || STATUS_CONFIG.available }, []) const getSupplyDeliveries = useCallback( (supply: Supply): Supply[] => { return supplies.filter((s) => s.name === supply.name && s.category === supply.category) }, [supplies], ) // Объединение одинаковых расходников const consolidatedSupplies = useMemo(() => { const grouped = supplies.reduce( (acc, supply) => { const key = `${supply.name}-${supply.category}` if (!acc[key]) { acc[key] = { ...supply, currentStock: 0, quantity: 0, // Общее количество поставленного (= заказанному) price: 0, totalCost: 0, // Общая стоимость shippedQuantity: 0, // Общее отправленное количество } } // Суммируем поставленное количество (заказано = поставлено) acc[key].quantity += supply.quantity // Суммируем отправленное количество acc[key].shippedQuantity += supply.shippedQuantity || 0 // Остаток = Поставлено - Отправлено // Если ничего не отправлено, то остаток = поставлено acc[key].currentStock = acc[key].quantity - acc[key].shippedQuantity // Рассчитываем общую стоимость (количество × цена) acc[key].totalCost += supply.quantity * supply.price // Средневзвешенная цена за единицу if (acc[key].quantity > 0) { acc[key].price = acc[key].totalCost / acc[key].quantity } return acc }, {} as Record, ) return Object.values(grouped) }, [supplies]) // Фильтрация и сортировка const filteredAndSortedSupplies = useMemo(() => { const filtered = consolidatedSupplies.filter((supply) => { const matchesSearch = supply.name.toLowerCase().includes(filters.search.toLowerCase()) || supply.description.toLowerCase().includes(filters.search.toLowerCase()) const matchesCategory = !filters.category || supply.category === filters.category const matchesStatus = !filters.status || supply.status === filters.status const matchesSupplier = !filters.supplier || supply.supplier.toLowerCase().includes(filters.supplier.toLowerCase()) const matchesLowStock = !filters.lowStock || (supply.currentStock <= supply.minStock && supply.currentStock > 0) return matchesSearch && matchesCategory && matchesStatus && matchesSupplier && matchesLowStock }) // Сортировка filtered.sort((a, b) => { let aValue: any = a[sort.field] let bValue: any = b[sort.field] if (typeof aValue === 'string') { aValue = aValue.toLowerCase() bValue = bValue.toLowerCase() } if (sort.direction === 'asc') { return aValue > bValue ? 1 : -1 } else { return aValue < bValue ? 1 : -1 } }) return filtered }, [consolidatedSupplies, filters, sort]) // Группировка const groupedSupplies = useMemo(() => { if (groupBy === 'none') return { 'Все расходники': filteredAndSortedSupplies } return filteredAndSortedSupplies.reduce( (acc, supply) => { const key = supply[groupBy] || 'Без категории' if (!acc[key]) acc[key] = [] acc[key].push(supply) return acc }, {} as Record, ) }, [filteredAndSortedSupplies, groupBy]) // Обработчики const handleSort = useCallback((field: SortState['field']) => { setSort((prev) => ({ field, direction: prev.field === field && prev.direction === 'asc' ? 'desc' : 'asc', })) }, []) const toggleSupplyExpansion = useCallback((supplyId: string) => { setExpandedSupplies((prev) => { const newSet = new Set(prev) if (newSet.has(supplyId)) { newSet.delete(supplyId) } else { newSet.add(supplyId) } return newSet }) }, []) const handleExport = useCallback(() => { const csvData = filteredAndSortedSupplies.map((supply) => ({ Название: supply.name, Описание: supply.description, Категория: supply.category, Статус: getStatusConfig(supply.status).label, 'Текущий остаток': supply.currentStock, 'Минимальный остаток': supply.minStock, Единица: supply.unit, Цена: supply.price, Поставщик: supply.supplier, 'Дата создания': new Date(supply.createdAt).toLocaleDateString('ru-RU'), })) const csv = [Object.keys(csvData[0]).join(','), ...csvData.map((row) => Object.values(row).join(','))].join('\n') const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }) const link = document.createElement('a') link.href = URL.createObjectURL(blob) link.download = `расходники_фулфилмента_${new Date().toISOString().split('T')[0]}.csv` link.click() toast.success('Данные экспортированы в CSV') }, [filteredAndSortedSupplies, getStatusConfig]) const handleRefresh = useCallback(() => { refetch() toast.success('Данные обновлены') }, [refetch]) if (loading) { return (
Загрузка...
) } if (error) { return (
Ошибка загрузки: {error.message}
) } return (
{/* Заголовок и фильтры */} setShowFilters(!showFilters)} onExport={handleExport} onRefresh={handleRefresh} /> {/* Статистика */} {/* Основной контент */}
{groupBy === 'none' ? ( // Без группировки <> {viewMode === 'grid' && ( )} {viewMode === 'list' && ( )} {viewMode === 'analytics' && (
Аналитический режим будет добавлен позже
)} ) : ( // С группировкой
{Object.entries(groupedSupplies).map(([groupName, groupSupplies]) => (

{groupName} ({groupSupplies.length})

{viewMode === 'grid' && ( )} {viewMode === 'list' && ( )}
))}
)}
) }