"use client"; import React, { useState, useMemo, useCallback } from "react"; import { Sidebar } from "@/components/dashboard/sidebar"; import { useSidebar } from "@/hooks/useSidebar"; import { useQuery } from "@apollo/client"; import { GET_MY_FULFILLMENT_SUPPLIES } from "@/graphql/queries"; import { Package, Wrench, AlertTriangle, CheckCircle, Clock, } from "lucide-react"; import { toast } from "sonner"; // Новые компоненты import { SuppliesHeader } from "./supplies-header"; import { SuppliesStats } from "./supplies-stats"; import { SuppliesGrid } from "./supplies-grid"; import { SuppliesList } from "./supplies-list"; // Типы import { Supply, FilterState, SortState, ViewMode, GroupBy, StatusConfig, } from "./types"; // Статусы расходников с цветами const STATUS_CONFIG = { 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, }, "in-transit": { label: "В пути", color: "bg-blue-500/20 text-blue-300", icon: Clock, }, 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.log("🔥🔥🔥 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(() => { let 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" && ( )}
) )}
)}
); }