"use client"; import React, { useState } from "react"; import { Card } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { ChevronDown, ChevronRight, Calendar, Package, MapPin, Building2, TrendingUp, AlertTriangle, DollarSign, Wrench, Box, } from "lucide-react"; // Типы данных для расходников interface ConsumableParameter { id: string; name: string; value: string; unit?: string; } interface Consumable { id: string; name: string; sku: string; category: string; type: "packaging" | "labels" | "protective" | "tools" | "other"; plannedQty: number; actualQty: number; defectQty: number; unitPrice: number; parameters: ConsumableParameter[]; } interface ConsumableSupplier { id: string; name: string; inn: string; contact: string; address: string; consumables: Consumable[]; totalAmount: number; } interface ConsumableRoute { id: string; from: string; fromAddress: string; to: string; toAddress: string; suppliers: ConsumableSupplier[]; totalConsumablesPrice: number; logisticsPrice: number; totalAmount: number; } interface ConsumableSupply { id: string; number: number; deliveryDate: string; createdDate: string; routes: ConsumableRoute[]; plannedTotal: number; actualTotal: number; defectTotal: number; totalConsumablesPrice: number; totalLogisticsPrice: number; grandTotal: number; status: "planned" | "in-transit" | "delivered" | "completed"; } // Моковые данные для расходников const mockConsumableSupplies: ConsumableSupply[] = [ { id: "c1", number: 101, deliveryDate: "2024-01-18", createdDate: "2024-01-14", status: "delivered", plannedTotal: 5000, actualTotal: 4950, defectTotal: 50, totalConsumablesPrice: 125000, totalLogisticsPrice: 8000, grandTotal: 133000, routes: [ { id: "cr1", from: "Склад расходников", fromAddress: "Москва, ул. Промышленная, 12", to: "SFERAV Logistics", toAddress: "Москва, ул. Складская, 15", totalConsumablesPrice: 125000, logisticsPrice: 8000, totalAmount: 133000, suppliers: [ { id: "cs1", name: 'ООО "УпакСервис"', inn: "7703456789", contact: "+7 (495) 777-88-99", address: "Москва, ул. Упаковочная, 5", totalAmount: 75000, consumables: [ { id: "cons1", name: "Коробки картонные 30x20x10", sku: "BOX-302010", category: "Упаковка", type: "packaging", plannedQty: 2000, actualQty: 1980, defectQty: 20, unitPrice: 35, parameters: [ { id: "cp1", name: "Размер", value: "30x20x10", unit: "см" }, { id: "cp2", name: "Материал", value: "Гофрокартон" }, { id: "cp3", name: "Плотность", value: "3", unit: "слоя" }, ], }, { id: "cons2", name: "Пузырчатая пленка", sku: "BUBBLE-100", category: "Защитная упаковка", type: "protective", plannedQty: 1000, actualQty: 1000, defectQty: 0, unitPrice: 25, parameters: [ { id: "cp4", name: "Ширина", value: "100", unit: "см" }, { id: "cp5", name: "Толщина", value: "0.1", unit: "мм" }, { id: "cp6", name: "Длина рулона", value: "50", unit: "м" }, ], }, ], }, { id: "cs2", name: "ИП Маркин С.А.", inn: "123456789013", contact: "+7 (499) 111-22-33", address: "Москва, пр-т Маркировочный, 8", totalAmount: 50000, consumables: [ { id: "cons3", name: "Этикетки самоклеящиеся", sku: "LABEL-5030", category: "Маркировка", type: "labels", plannedQty: 2000, actualQty: 1970, defectQty: 30, unitPrice: 15, parameters: [ { id: "cp7", name: "Размер", value: "50x30", unit: "мм" }, { id: "cp8", name: "Материал", value: "Полипропилен" }, { id: "cp9", name: "Клей", value: "Акриловый" }, ], }, ], }, ], }, ], }, { id: "c2", number: 102, deliveryDate: "2024-01-22", createdDate: "2024-01-16", status: "in-transit", plannedTotal: 1500, actualTotal: 1500, defectTotal: 0, totalConsumablesPrice: 45000, totalLogisticsPrice: 3000, grandTotal: 48000, routes: [ { id: "cr2", from: "Инструментальный склад", fromAddress: "Подольск, ул. Индустриальная, 25", to: "WB Подольск", toAddress: "Подольск, ул. Складская, 25", totalConsumablesPrice: 45000, logisticsPrice: 3000, totalAmount: 48000, suppliers: [ { id: "cs3", name: 'ООО "ИнструментПро"', inn: "5001234567", contact: "+7 (4967) 55-66-77", address: "Подольск, ул. Инструментальная, 15", totalAmount: 45000, consumables: [ { id: "cons4", name: "Сканер штрих-кодов", sku: "SCANNER-2D", category: "Оборудование", type: "tools", plannedQty: 5, actualQty: 5, defectQty: 0, unitPrice: 8000, parameters: [ { id: "cp10", name: "Тип", value: "2D сканер" }, { id: "cp11", name: "Интерфейс", value: "USB" }, { id: "cp12", name: "Дальность", value: "30", unit: "см" }, ], }, { id: "cons5", name: "Термопринтер этикеток", sku: "PRINTER-THERMAL", category: "Оборудование", type: "tools", plannedQty: 2, actualQty: 2, defectQty: 0, unitPrice: 2500, parameters: [ { id: "cp13", name: "Ширина печати", value: "108", unit: "мм", }, { id: "cp14", name: "Разрешение", value: "203", unit: "dpi" }, { id: "cp15", name: "Скорость", value: "102", unit: "мм/с" }, ], }, ], }, ], }, ], }, ]; export function SuppliesConsumablesTab() { const [expandedSupplies, setExpandedSupplies] = useState>( new Set() ); const [expandedRoutes, setExpandedRoutes] = useState>(new Set()); const [expandedSuppliers, setExpandedSuppliers] = useState>( new Set() ); const [expandedConsumables, setExpandedConsumables] = useState>( new Set() ); const toggleSupplyExpansion = (supplyId: string) => { const newExpanded = new Set(expandedSupplies); if (newExpanded.has(supplyId)) { newExpanded.delete(supplyId); } else { newExpanded.add(supplyId); } setExpandedSupplies(newExpanded); }; const toggleRouteExpansion = (routeId: string) => { const newExpanded = new Set(expandedRoutes); if (newExpanded.has(routeId)) { newExpanded.delete(routeId); } else { newExpanded.add(routeId); } setExpandedRoutes(newExpanded); }; const toggleSupplierExpansion = (supplierId: string) => { const newExpanded = new Set(expandedSuppliers); if (newExpanded.has(supplierId)) { newExpanded.delete(supplierId); } else { newExpanded.add(supplierId); } setExpandedSuppliers(newExpanded); }; const toggleConsumableExpansion = (consumableId: string) => { const newExpanded = new Set(expandedConsumables); if (newExpanded.has(consumableId)) { newExpanded.delete(consumableId); } else { newExpanded.add(consumableId); } setExpandedConsumables(newExpanded); }; const getStatusBadge = (status: ConsumableSupply["status"]) => { const statusMap = { planned: { label: "Запланирована", color: "bg-blue-500/20 text-blue-300 border-blue-500/30", }, "in-transit": { label: "В пути", color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30", }, delivered: { label: "Доставлена", color: "bg-green-500/20 text-green-300 border-green-500/30", }, completed: { label: "Завершена", color: "bg-purple-500/20 text-purple-300 border-purple-500/30", }, }; const { label, color } = statusMap[status]; return {label}; }; const getTypeBadge = (type: Consumable["type"]) => { const typeMap = { packaging: { label: "Упаковка", color: "bg-blue-500/20 text-blue-300 border-blue-500/30", }, labels: { label: "Этикетки", color: "bg-green-500/20 text-green-300 border-green-500/30", }, protective: { label: "Защитная", color: "bg-orange-500/20 text-orange-300 border-orange-500/30", }, tools: { label: "Инструменты", color: "bg-purple-500/20 text-purple-300 border-purple-500/30", }, other: { label: "Прочее", color: "bg-gray-500/20 text-gray-300 border-gray-500/30", }, }; const { label, color } = typeMap[type]; return {label}; }; const formatCurrency = (amount: number) => { return new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB", minimumFractionDigits: 0, }).format(amount); }; const formatDate = (dateString: string) => { return new Date(dateString).toLocaleDateString("ru-RU", { day: "2-digit", month: "2-digit", year: "numeric", }); }; const calculateConsumableTotal = (consumable: Consumable) => { return consumable.actualQty * consumable.unitPrice; }; const getEfficiencyBadge = ( planned: number, actual: number, defect: number ) => { const efficiency = ((actual - defect) / planned) * 100; if (efficiency >= 95) { return ( Отлично ); } else if (efficiency >= 90) { return ( Хорошо ); } else { return ( Проблемы ); } }; return (
{/* Статистика расходников */}

Поставок расходников

{mockConsumableSupplies.length}

Сумма расходников

{formatCurrency( mockConsumableSupplies.reduce( (sum, supply) => sum + supply.grandTotal, 0 ) )}

В пути

{ mockConsumableSupplies.filter( (supply) => supply.status === "in-transit" ).length }

С браком

{ mockConsumableSupplies.filter( (supply) => supply.defectTotal > 0 ).length }

{/* Таблица поставок расходников */}
{mockConsumableSupplies.map((supply) => { const isSupplyExpanded = expandedSupplies.has(supply.id); return ( {/* Основная строка поставки расходников */} toggleSupplyExpansion(supply.id)} > {/* Развернутые уровни */} {isSupplyExpanded && supply.routes.map((route) => { const isRouteExpanded = expandedRoutes.has(route.id); return ( {/* Поставщики */} {isRouteExpanded && route.suppliers.map((supplier) => { const isSupplierExpanded = expandedSuppliers.has(supplier.id); return ( {/* Расходники */} {isSupplierExpanded && supplier.consumables.map((consumable) => { const isConsumableExpanded = expandedConsumables.has( consumable.id ); return ( {/* Параметры расходника */} {isConsumableExpanded && ( )} ); })} ); })} ); })} ); })}
Дата поставки Дата создания План Факт Брак Цена расходников Логистика Итого сумма Статус
{supply.number}
{formatDate(supply.deliveryDate)}
{formatDate(supply.createdDate)} {supply.plannedTotal} {supply.actualTotal} 0 ? "text-red-400" : "text-white" }`} > {supply.defectTotal} {formatCurrency(supply.totalConsumablesPrice)} {formatCurrency(supply.totalLogisticsPrice)}
{formatCurrency(supply.grandTotal)}
{getStatusBadge(supply.status)}
Маршрут
{route.from} {route.to}
{route.fromAddress} → {route.toAddress}
{route.suppliers.reduce( (sum, s) => sum + s.consumables.reduce( (cSum, c) => cSum + c.plannedQty, 0 ), 0 )} {route.suppliers.reduce( (sum, s) => sum + s.consumables.reduce( (cSum, c) => cSum + c.actualQty, 0 ), 0 )} {route.suppliers.reduce( (sum, s) => sum + s.consumables.reduce( (cSum, c) => cSum + c.defectQty, 0 ), 0 )} {formatCurrency(route.totalConsumablesPrice)} {formatCurrency(route.logisticsPrice)} {formatCurrency(route.totalAmount)}
Поставщик
{supplier.name}
ИНН: {supplier.inn}
{supplier.address}
{supplier.contact}
{supplier.consumables.reduce( (sum, c) => sum + c.plannedQty, 0 )} {supplier.consumables.reduce( (sum, c) => sum + c.actualQty, 0 )} {supplier.consumables.reduce( (sum, c) => sum + c.defectQty, 0 )} {formatCurrency( supplier.consumables.reduce( (sum, c) => sum + calculateConsumableTotal(c), 0 ) )} {formatCurrency(supplier.totalAmount)}
Расходник
{consumable.name}
Артикул: {consumable.sku}
{consumable.category} {getTypeBadge( consumable.type )}
{consumable.plannedQty} {consumable.actualQty} 0 ? "text-red-400" : "text-white" }`} > {consumable.defectQty}
{formatCurrency( calculateConsumableTotal( consumable ) )}
{formatCurrency( consumable.unitPrice )}{" "} за шт.
{getEfficiencyBadge( consumable.plannedQty, consumable.actualQty, consumable.defectQty )} {formatCurrency( calculateConsumableTotal( consumable ) )}

📋 Параметры расходника:

{consumable.parameters.map( (param) => (
{param.name}
{param.value}{" "} {param.unit || ""}
) )}
); }