'use client'
import {
Package,
Building2,
Calendar,
DollarSign,
Search,
Filter,
ChevronDown,
ChevronRight,
Smartphone,
Eye,
MoreHorizontal,
MapPin,
TrendingUp,
AlertTriangle,
Warehouse,
} from 'lucide-react'
import React, { useState } from 'react'
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 { formatCurrency } from '@/lib/utils'
// Простые компоненты таблицы
const Table = ({ children, ...props }: any) => (
)
const TableHeader = ({ children, ...props }: any) => {children}
const TableBody = ({ children, ...props }: any) => {children}
const TableRow = ({ children, className, ...props }: any) => (
{children}
)
const TableHead = ({ children, className, ...props }: any) => (
{children}
|
)
const TableCell = ({ children, className, ...props }: any) => (
{children}
|
)
// Расширенные типы данных для детальной структуры поставок
interface ProductParameter {
id: string
name: string
value: string
unit?: string
}
interface GoodsSupplyProduct {
id: string
name: string
sku: string
category: string
plannedQty: number
actualQty: number
defectQty: number
productPrice: number
parameters: ProductParameter[]
}
interface GoodsSupplyWholesaler {
id: string
name: string
inn: string
contact: string
address: string
products: GoodsSupplyProduct[]
totalAmount: number
}
interface GoodsSupplyRoute {
id: string
from: string
fromAddress: string
to: string
toAddress: string
wholesalers: GoodsSupplyWholesaler[]
totalProductPrice: number
fulfillmentServicePrice: number
logisticsPrice: number
totalAmount: number
}
// Основной интерфейс поставки товаров согласно rules2.md 9.5.4
interface GoodsSupply {
id: string
number: string
creationMethod: 'cards' | 'suppliers' // 📱 карточки / 🏢 поставщик
deliveryDate: string
createdAt: string
status: string
// Агрегированные данные
plannedTotal: number
actualTotal: number
defectTotal: number
totalProductPrice: number
totalFulfillmentPrice: number
totalLogisticsPrice: number
grandTotal: number
// Детальная структура
routes: GoodsSupplyRoute[]
// Для обратной совместимости
goodsCount?: number
totalAmount?: number
supplier?: string
items?: GoodsSupplyItem[]
}
// Простой интерфейс товара для базовой детализации
interface GoodsSupplyItem {
id: string
name: string
quantity: number
price: number
category?: string
}
interface GoodsSuppliesTableProps {
supplies?: GoodsSupply[]
loading?: boolean
}
// Компонент для иконки способа создания
function CreationMethodIcon({ method }: { method: 'cards' | 'suppliers' }) {
if (method === 'cards') {
return (
Карточки
)
}
return (
Поставщик
)
}
// Компонент для статуса поставки
function StatusBadge({ status }: { status: string }) {
const getStatusColor = (status: string) => {
switch (status.toLowerCase()) {
case 'pending':
return 'bg-yellow-500/20 text-yellow-300 border-yellow-500/30'
case 'supplier_approved':
return 'bg-blue-500/20 text-blue-300 border-blue-500/30'
case 'confirmed':
return 'bg-purple-500/20 text-purple-300 border-purple-500/30'
case 'shipped':
return 'bg-orange-500/20 text-orange-300 border-orange-500/30'
case 'in_transit':
return 'bg-indigo-500/20 text-indigo-300 border-indigo-500/30'
case 'delivered':
return 'bg-green-500/20 text-green-300 border-green-500/30'
default:
return 'bg-gray-500/20 text-gray-300 border-gray-500/30'
}
}
const getStatusText = (status: string) => {
switch (status.toLowerCase()) {
case 'pending':
return 'Ожидает'
case 'supplier_approved':
return 'Одобрена'
case 'confirmed':
return 'Подтверждена'
case 'shipped':
return 'Отгружена'
case 'in_transit':
return 'В пути'
case 'delivered':
return 'Доставлена'
default:
return status
}
}
return {getStatusText(status)}
}
export function GoodsSuppliesTable({ supplies = [], loading = false }: GoodsSuppliesTableProps) {
const [searchQuery, setSearchQuery] = useState('')
const [selectedMethod, setSelectedMethod] = useState('all')
const [selectedStatus, setSelectedStatus] = useState('all')
const [expandedSupplies, setExpandedSupplies] = useState>(new Set())
const [expandedRoutes, setExpandedRoutes] = useState>(new Set())
const [expandedWholesalers, setExpandedWholesalers] = useState>(new Set())
const [expandedProducts, setExpandedProducts] = useState>(new Set())
// Фильтрация согласно rules2.md 9.5.4 с поддержкой расширенной структуры
const filteredSupplies = supplies.filter((supply) => {
const matchesSearch =
supply.number.toLowerCase().includes(searchQuery.toLowerCase()) ||
(supply.supplier && supply.supplier.toLowerCase().includes(searchQuery.toLowerCase())) ||
(supply.routes &&
supply.routes.some((route) =>
route.wholesalers.some((wholesaler) => wholesaler.name.toLowerCase().includes(searchQuery.toLowerCase())),
))
const matchesMethod = selectedMethod === 'all' || supply.creationMethod === selectedMethod
const matchesStatus = selectedStatus === 'all' || supply.status === selectedStatus
return matchesSearch && matchesMethod && matchesStatus
})
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 toggleWholesalerExpansion = (wholesalerId: string) => {
const newExpanded = new Set(expandedWholesalers)
if (newExpanded.has(wholesalerId)) {
newExpanded.delete(wholesalerId)
} else {
newExpanded.add(wholesalerId)
}
setExpandedWholesalers(newExpanded)
}
const toggleProductExpansion = (productId: string) => {
const newExpanded = new Set(expandedProducts)
if (newExpanded.has(productId)) {
newExpanded.delete(productId)
} else {
newExpanded.add(productId)
}
setExpandedProducts(newExpanded)
}
// Вспомогательные функции
const getStatusBadge = (status: string) => {
const statusMap = {
pending: { label: 'Ожидает', color: 'bg-yellow-500/20 text-yellow-300 border-yellow-500/30' },
supplier_approved: { label: 'Одобрена', color: 'bg-blue-500/20 text-blue-300 border-blue-500/30' },
confirmed: { label: 'Подтверждена', color: 'bg-purple-500/20 text-purple-300 border-purple-500/30' },
shipped: { label: 'Отгружена', color: 'bg-orange-500/20 text-orange-300 border-orange-500/30' },
in_transit: { label: 'В пути', color: 'bg-indigo-500/20 text-indigo-300 border-indigo-500/30' },
delivered: { label: 'Доставлена', color: 'bg-green-500/20 text-green-300 border-green-500/30' },
planned: { label: 'Запланирована', color: 'bg-blue-500/20 text-blue-300 border-blue-500/30' },
completed: { label: 'Завершена', color: 'bg-purple-500/20 text-purple-300 border-purple-500/30' },
}
const statusInfo = statusMap[status as keyof typeof statusMap] || {
label: status,
color: 'bg-gray-500/20 text-gray-300 border-gray-500/30',
}
return {statusInfo.label}
}
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 Проблемы
}
}
const calculateProductTotal = (product: GoodsSupplyProduct) => {
return product.actualQty * product.productPrice
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
}
if (loading) {
return (
{[...Array(5)].map((_, i) => (
))}
)
}
return (
{/* Фильтры */}
{/* Поиск */}
setSearchQuery(e.target.value)}
className="bg-white/10 border-white/20 text-white placeholder-white/50 pl-10"
/>
{/* Фильтр по способу создания */}
{/* Фильтр по статусу */}
{/* Таблица поставок согласно rules2.md 9.5.4 */}
№
Дата поставки
Поставка
Создана
План
Факт
Брак
Цена товаров
Цена
ФФ
Логистика
Итого
Статус
Способ
{filteredSupplies.length === 0 ? (
{searchQuery || selectedMethod !== 'all' || selectedStatus !== 'all'
? 'Поставки не найдены по заданным фильтрам'
: 'Поставки товаров отсутствуют'}
) : (
filteredSupplies.map((supply) => {
const isSupplyExpanded = expandedSupplies.has(supply.id)
return (
{/* Основная строка поставки */}
toggleSupplyExpansion(supply.id)}
>
{isSupplyExpanded ? (
) : (
)}
{supply.number}
{formatDate(supply.deliveryDate)}
{formatDate(supply.createdAt)}
{supply.plannedTotal || supply.goodsCount || 0}
{supply.actualTotal || supply.goodsCount || 0}
0 ? 'text-red-400' : 'text-white'
}`}
>
{supply.defectTotal || 0}
{formatCurrency(supply.totalProductPrice || supply.totalAmount || 0)}
{formatCurrency(supply.totalFulfillmentPrice || 0)}
{formatCurrency(supply.totalLogisticsPrice || 0)}
{formatCurrency(supply.grandTotal || supply.totalAmount || 0)}
{getStatusBadge(supply.status)}
{/* Развернутые уровни - маршруты, поставщики, товары */}
{isSupplyExpanded &&
supply.routes &&
supply.routes.map((route) => {
const isRouteExpanded = expandedRoutes.has(route.id)
return (
toggleRouteExpansion(route.id)}
>
{route.from}
→
{route.to}
{route.fromAddress} → {route.toAddress}
{route.wholesalers.reduce(
(sum, w) => sum + w.products.reduce((pSum, p) => pSum + p.plannedQty, 0),
0,
)}
{route.wholesalers.reduce(
(sum, w) => sum + w.products.reduce((pSum, p) => pSum + p.actualQty, 0),
0,
)}
{route.wholesalers.reduce(
(sum, w) => sum + w.products.reduce((pSum, p) => pSum + p.defectQty, 0),
0,
)}
{formatCurrency(route.totalProductPrice)}
{formatCurrency(route.fulfillmentServicePrice)}
{formatCurrency(route.logisticsPrice)}
{formatCurrency(route.totalAmount)}
{/* Поставщики в маршруте */}
{isRouteExpanded &&
route.wholesalers.map((wholesaler) => {
const isWholesalerExpanded = expandedWholesalers.has(wholesaler.id)
return (
toggleWholesalerExpansion(wholesaler.id)}
>
{wholesaler.name}
ИНН: {wholesaler.inn}
{wholesaler.address}
{wholesaler.contact}
{wholesaler.products.reduce((sum, p) => sum + p.plannedQty, 0)}
{wholesaler.products.reduce((sum, p) => sum + p.actualQty, 0)}
{wholesaler.products.reduce((sum, p) => sum + p.defectQty, 0)}
{formatCurrency(
wholesaler.products.reduce((sum, p) => sum + calculateProductTotal(p), 0),
)}
{formatCurrency(wholesaler.totalAmount)}
{/* Товары поставщика */}
{isWholesalerExpanded &&
wholesaler.products.map((product) => {
const isProductExpanded = expandedProducts.has(product.id)
return (
toggleProductExpansion(product.id)}
>
{product.name}
Артикул: {product.sku}
{product.category}
{product.plannedQty}
{product.actualQty}
0 ? 'text-red-400' : 'text-white'
}`}
>
{product.defectQty}
{formatCurrency(calculateProductTotal(product))}
{formatCurrency(product.productPrice)} за шт.
{getEfficiencyBadge(
product.plannedQty,
product.actualQty,
product.defectQty,
)}
{formatCurrency(calculateProductTotal(product))}
{/* Параметры товара */}
{isProductExpanded && (
📋 Параметры товара:
{product.parameters.map((param) => (
{param.name}
{param.value} {param.unit || ''}
))}
)}
)
})}
)
})}
)
})}
{/* Базовая детализация для поставок без маршрутов */}
{isSupplyExpanded && supply.items && !supply.routes && (
Детализация товаров:
{supply.items.map((item) => (
{item.name}
{item.category && (
({item.category})
)}
{item.quantity} шт
{formatCurrency(item.price)}
{formatCurrency(item.price * item.quantity)}
))}
)}
)
})
)}
)
}