Files
sfera-new/src/components/supplies/goods-supplies-table.tsx
Veronika Smirnova bf27f3ba29 Оптимизирована производительность React компонентов с помощью мемоизации
КРИТИЧНЫЕ КОМПОНЕНТЫ ОПТИМИЗИРОВАНЫ:
• AdminDashboard (346 kB) - добавлены React.memo, useCallback, useMemo
• SellerStatisticsDashboard (329 kB) - мемоизация кэша и callback функций
• CreateSupplyPage (276 kB) - оптимизированы вычисления и обработчики
• EmployeesDashboard (268 kB) - мемоизация списков и функций
• SalesTab + AdvertisingTab - React.memo обертка

ТЕХНИЧЕСКИЕ УЛУЧШЕНИЯ:
 React.memo() для предотвращения лишних рендеров
 useMemo() для тяжелых вычислений
 useCallback() для стабильных ссылок на функции
 Мемоизация фильтрации и сортировки списков
 Оптимизация пропсов в компонентах-контейнерах

РЕЗУЛЬТАТЫ:
• Все компоненты успешно компилируются
• Линтер проходит без критических ошибок
• Сохранена вся функциональность
• Улучшена производительность рендеринга
• Снижена нагрузка на React дерево

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-06 13:18:45 +03:00

777 lines
38 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 {
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) => (
<div className="w-full overflow-auto" {...props}>
<table className="w-full">{children}</table>
</div>
)
const TableHeader = ({ children, ...props }: any) => <thead {...props}>{children}</thead>
const TableBody = ({ children, ...props }: any) => <tbody {...props}>{children}</tbody>
const TableRow = ({ children, className, ...props }: any) => (
<tr className={className} {...props}>
{children}
</tr>
)
const TableHead = ({ children, className, ...props }: any) => (
<th className={`px-4 py-3 text-left font-medium ${className}`} {...props}>
{children}
</th>
)
const TableCell = ({ children, className, ...props }: any) => (
<td className={`px-4 py-3 ${className}`} {...props}>
{children}
</td>
)
// Расширенные типы данных для детальной структуры поставок
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 (
<div className="flex items-center gap-1 text-blue-400">
<Smartphone className="h-3 w-3" />
<span className="text-xs hidden sm:inline">Карточки</span>
</div>
)
}
return (
<div className="flex items-center gap-1 text-green-400">
<Building2 className="h-3 w-3" />
<span className="text-xs hidden sm:inline">Поставщик</span>
</div>
)
}
// Компонент для статуса поставки
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 <Badge className={`${getStatusColor(status)} border text-xs`}>{getStatusText(status)}</Badge>
}
export function GoodsSuppliesTable({ supplies = [], loading = false }: GoodsSuppliesTableProps) {
const [searchQuery, setSearchQuery] = useState('')
const [selectedMethod, setSelectedMethod] = useState<string>('all')
const [selectedStatus, setSelectedStatus] = useState<string>('all')
const [expandedSupplies, setExpandedSupplies] = useState<Set<string>>(new Set())
const [expandedRoutes, setExpandedRoutes] = useState<Set<string>>(new Set())
const [expandedWholesalers, setExpandedWholesalers] = useState<Set<string>>(new Set())
const [expandedProducts, setExpandedProducts] = useState<Set<string>>(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 <Badge className={`${statusInfo.color} border`}>{statusInfo.label}</Badge>
}
const getEfficiencyBadge = (planned: number, actual: number, defect: number) => {
const efficiency = ((actual - defect) / planned) * 100
if (efficiency >= 95) {
return <Badge className="bg-green-500/20 text-green-300 border-green-500/30 border">Отлично</Badge>
} else if (efficiency >= 90) {
return <Badge className="bg-yellow-500/20 text-yellow-300 border-yellow-500/30 border">Хорошо</Badge>
} else {
return <Badge className="bg-red-500/20 text-red-300 border-red-500/30 border">Проблемы</Badge>
}
}
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 (
<Card className="bg-white/10 backdrop-blur border-white/20 p-6">
<div className="animate-pulse space-y-4">
<div className="h-4 bg-white/10 rounded w-1/4"></div>
<div className="space-y-2">
{[...Array(5)].map((_, i) => (
<div key={i} className="h-12 bg-white/5 rounded"></div>
))}
</div>
</div>
</Card>
)
}
return (
<div className="space-y-4">
{/* Фильтры */}
<Card className="bg-white/5 backdrop-blur border-white/10 p-4">
<div className="flex flex-col sm:flex-row gap-4">
{/* Поиск */}
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/40 h-4 w-4" />
<Input
placeholder="Поиск по номеру или поставщику..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="bg-white/10 border-white/20 text-white placeholder-white/50 pl-10"
/>
</div>
{/* Фильтр по способу создания */}
<select
value={selectedMethod}
onChange={(e) => setSelectedMethod(e.target.value)}
className="bg-white/10 border border-white/20 text-white rounded-md px-3 py-2 text-sm"
>
<option value="all">Все способы</option>
<option value="cards">Карточки</option>
<option value="suppliers">Поставщики</option>
</select>
{/* Фильтр по статусу */}
<select
value={selectedStatus}
onChange={(e) => setSelectedStatus(e.target.value)}
className="bg-white/10 border border-white/20 text-white rounded-md px-3 py-2 text-sm"
>
<option value="all">Все статусы</option>
<option value="pending">Ожидает</option>
<option value="supplier_approved">Одобрена</option>
<option value="confirmed">Подтверждена</option>
<option value="shipped">Отгружена</option>
<option value="in_transit">В пути</option>
<option value="delivered">Доставлена</option>
</select>
</div>
</Card>
{/* Таблица поставок согласно rules2.md 9.5.4 */}
<Card className="bg-white/10 backdrop-blur border-white/20 overflow-hidden">
<Table>
<TableHeader>
<TableRow className="border-white/10 hover:bg-white/5">
<TableHead className="text-white/70"></TableHead>
<TableHead className="text-white/70">
<span className="hidden sm:inline">Дата поставки</span>
<span className="sm:hidden">Поставка</span>
</TableHead>
<TableHead className="text-white/70 hidden lg:table-cell">Создана</TableHead>
<TableHead className="text-white/70">План</TableHead>
<TableHead className="text-white/70">Факт</TableHead>
<TableHead className="text-white/70">Брак</TableHead>
<TableHead className="text-white/70">
<span className="hidden md:inline">Цена товаров</span>
<span className="md:hidden">Цена</span>
</TableHead>
<TableHead className="text-white/70 hidden lg:table-cell">ФФ</TableHead>
<TableHead className="text-white/70 hidden lg:table-cell">Логистика</TableHead>
<TableHead className="text-white/70">Итого</TableHead>
<TableHead className="text-white/70">Статус</TableHead>
<TableHead className="text-white/70">Способ</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredSupplies.length === 0 ? (
<TableRow>
<TableCell colSpan={12} className="text-center py-8 text-white/60">
{searchQuery || selectedMethod !== 'all' || selectedStatus !== 'all'
? 'Поставки не найдены по заданным фильтрам'
: 'Поставки товаров отсутствуют'}
</TableCell>
</TableRow>
) : (
filteredSupplies.map((supply) => {
const isSupplyExpanded = expandedSupplies.has(supply.id)
return (
<React.Fragment key={supply.id}>
{/* Основная строка поставки */}
<TableRow
className="border-white/10 hover:bg-white/5 cursor-pointer transition-colors bg-purple-500/10"
onClick={() => toggleSupplyExpansion(supply.id)}
>
<TableCell className="text-white font-mono text-sm">
<div className="flex items-center gap-2">
{isSupplyExpanded ? (
<ChevronDown className="h-4 w-4 text-white/40" />
) : (
<ChevronRight className="h-4 w-4 text-white/40" />
)}
{supply.number}
</div>
</TableCell>
<TableCell>
<div className="flex items-center space-x-1">
<Calendar className="h-3 w-3 text-white/40" />
<span className="text-white font-semibold text-sm">{formatDate(supply.deliveryDate)}</span>
</div>
</TableCell>
<TableCell className="hidden lg:table-cell">
<span className="text-white/80 text-sm">{formatDate(supply.createdAt)}</span>
</TableCell>
<TableCell>
<span className="text-white font-semibold text-sm">
{supply.plannedTotal || supply.goodsCount || 0}
</span>
</TableCell>
<TableCell>
<span className="text-white font-semibold text-sm">
{supply.actualTotal || supply.goodsCount || 0}
</span>
</TableCell>
<TableCell>
<span
className={`font-semibold text-sm ${
(supply.defectTotal || 0) > 0 ? 'text-red-400' : 'text-white'
}`}
>
{supply.defectTotal || 0}
</span>
</TableCell>
<TableCell>
<span className="text-green-400 font-semibold text-sm">
{formatCurrency(supply.totalProductPrice || supply.totalAmount || 0)}
</span>
</TableCell>
<TableCell className="hidden lg:table-cell">
<span className="text-blue-400 font-semibold text-sm">
{formatCurrency(supply.totalFulfillmentPrice || 0)}
</span>
</TableCell>
<TableCell className="hidden lg:table-cell">
<span className="text-purple-400 font-semibold text-sm">
{formatCurrency(supply.totalLogisticsPrice || 0)}
</span>
</TableCell>
<TableCell>
<div className="flex items-center space-x-1">
<DollarSign className="h-3 w-3 text-white/40" />
<span className="text-white font-bold text-sm">
{formatCurrency(supply.grandTotal || supply.totalAmount || 0)}
</span>
</div>
</TableCell>
<TableCell>{getStatusBadge(supply.status)}</TableCell>
<TableCell>
<CreationMethodIcon method={supply.creationMethod} />
</TableCell>
</TableRow>
{/* Развернутые уровни - маршруты, поставщики, товары */}
{isSupplyExpanded &&
supply.routes &&
supply.routes.map((route) => {
const isRouteExpanded = expandedRoutes.has(route.id)
return (
<React.Fragment key={route.id}>
<TableRow
className="border-white/10 hover:bg-white/5 cursor-pointer transition-colors bg-blue-500/10"
onClick={() => toggleRouteExpansion(route.id)}
>
<TableCell className="relative">
<div className="flex items-center space-x-2">
<div className="w-1 h-1 rounded-full bg-blue-400 mr-1"></div>
<MapPin className="h-3 w-3 text-blue-400" />
<span className="text-white font-medium text-sm">Маршрут</span>
</div>
<div className="absolute left-0 top-0 w-0.5 h-full bg-blue-400/30"></div>
</TableCell>
<TableCell colSpan={1}>
<div className="text-white">
<div className="flex items-center space-x-2 mb-1">
<span className="font-medium text-sm">{route.from}</span>
<span className="text-white/60"></span>
<span className="font-medium text-sm">{route.to}</span>
</div>
<div className="text-xs text-white/60 hidden sm:block">
{route.fromAddress} {route.toAddress}
</div>
</div>
</TableCell>
<TableCell className="hidden lg:table-cell"></TableCell>
<TableCell>
<span className="text-white/80 text-sm">
{route.wholesalers.reduce(
(sum, w) => sum + w.products.reduce((pSum, p) => pSum + p.plannedQty, 0),
0,
)}
</span>
</TableCell>
<TableCell>
<span className="text-white/80 text-sm">
{route.wholesalers.reduce(
(sum, w) => sum + w.products.reduce((pSum, p) => pSum + p.actualQty, 0),
0,
)}
</span>
</TableCell>
<TableCell>
<span className="text-white/80 text-sm">
{route.wholesalers.reduce(
(sum, w) => sum + w.products.reduce((pSum, p) => pSum + p.defectQty, 0),
0,
)}
</span>
</TableCell>
<TableCell>
<span className="text-green-400 font-medium text-sm">
{formatCurrency(route.totalProductPrice)}
</span>
</TableCell>
<TableCell className="hidden lg:table-cell">
<span className="text-blue-400 font-medium text-sm">
{formatCurrency(route.fulfillmentServicePrice)}
</span>
</TableCell>
<TableCell className="hidden lg:table-cell">
<span className="text-purple-400 font-medium text-sm">
{formatCurrency(route.logisticsPrice)}
</span>
</TableCell>
<TableCell>
<span className="text-white font-semibold text-sm">
{formatCurrency(route.totalAmount)}
</span>
</TableCell>
<TableCell colSpan={2}></TableCell>
</TableRow>
{/* Поставщики в маршруте */}
{isRouteExpanded &&
route.wholesalers.map((wholesaler) => {
const isWholesalerExpanded = expandedWholesalers.has(wholesaler.id)
return (
<React.Fragment key={wholesaler.id}>
<TableRow
className="border-white/10 hover:bg-white/5 cursor-pointer transition-colors bg-green-500/10"
onClick={() => toggleWholesalerExpansion(wholesaler.id)}
>
<TableCell className="relative">
<div className="flex items-center space-x-2">
<div className="w-1 h-1 rounded-full bg-green-400 mr-1"></div>
<div className="w-1 h-1 rounded-full bg-green-400 mr-1"></div>
<Building2 className="h-3 w-3 text-green-400" />
<span className="text-white font-medium text-sm">Поставщик</span>
</div>
<div className="absolute left-0 top-0 w-0.5 h-full bg-green-400/30"></div>
</TableCell>
<TableCell colSpan={1}>
<div className="text-white">
<div className="font-medium mb-1 text-sm">{wholesaler.name}</div>
<div className="text-xs text-white/60 mb-1 hidden sm:block">
ИНН: {wholesaler.inn}
</div>
<div className="text-xs text-white/60 mb-1 hidden lg:block">
{wholesaler.address}
</div>
<div className="text-xs text-white/60 hidden sm:block">
{wholesaler.contact}
</div>
</div>
</TableCell>
<TableCell className="hidden lg:table-cell"></TableCell>
<TableCell>
<span className="text-white/80 text-sm">
{wholesaler.products.reduce((sum, p) => sum + p.plannedQty, 0)}
</span>
</TableCell>
<TableCell>
<span className="text-white/80 text-sm">
{wholesaler.products.reduce((sum, p) => sum + p.actualQty, 0)}
</span>
</TableCell>
<TableCell>
<span className="text-white/80 text-sm">
{wholesaler.products.reduce((sum, p) => sum + p.defectQty, 0)}
</span>
</TableCell>
<TableCell>
<span className="text-green-400 font-medium text-sm">
{formatCurrency(
wholesaler.products.reduce((sum, p) => sum + calculateProductTotal(p), 0),
)}
</span>
</TableCell>
<TableCell className="hidden lg:table-cell" colSpan={2}></TableCell>
<TableCell>
<span className="text-white font-semibold text-sm">
{formatCurrency(wholesaler.totalAmount)}
</span>
</TableCell>
<TableCell colSpan={2}></TableCell>
</TableRow>
{/* Товары поставщика */}
{isWholesalerExpanded &&
wholesaler.products.map((product) => {
const isProductExpanded = expandedProducts.has(product.id)
return (
<React.Fragment key={product.id}>
<TableRow
className="border-white/10 hover:bg-white/5 cursor-pointer transition-colors bg-yellow-500/10"
onClick={() => toggleProductExpansion(product.id)}
>
<TableCell className="relative">
<div className="flex items-center space-x-2">
<div className="w-1 h-1 rounded-full bg-yellow-400 mr-1"></div>
<div className="w-1 h-1 rounded-full bg-yellow-400 mr-1"></div>
<div className="w-1 h-1 rounded-full bg-yellow-400 mr-1"></div>
<Package className="h-3 w-3 text-yellow-400" />
<span className="text-white font-medium text-sm">Товар</span>
</div>
<div className="absolute left-0 top-0 w-0.5 h-full bg-yellow-400/30"></div>
</TableCell>
<TableCell colSpan={1}>
<div className="text-white">
<div className="font-medium mb-1 text-sm">{product.name}</div>
<div className="text-xs text-white/60 mb-1 hidden sm:block">
Артикул: {product.sku}
</div>
<Badge className="bg-gray-500/20 text-gray-300 border-gray-500/30 border text-xs hidden sm:inline-flex">
{product.category}
</Badge>
</div>
</TableCell>
<TableCell className="hidden lg:table-cell"></TableCell>
<TableCell>
<span className="text-white font-semibold text-sm">
{product.plannedQty}
</span>
</TableCell>
<TableCell>
<span className="text-white font-semibold text-sm">
{product.actualQty}
</span>
</TableCell>
<TableCell>
<span
className={`font-semibold text-sm ${
product.defectQty > 0 ? 'text-red-400' : 'text-white'
}`}
>
{product.defectQty}
</span>
</TableCell>
<TableCell>
<div className="text-white">
<div className="font-medium text-sm">
{formatCurrency(calculateProductTotal(product))}
</div>
<div className="text-xs text-white/60 hidden sm:block">
{formatCurrency(product.productPrice)} за шт.
</div>
</div>
</TableCell>
<TableCell className="hidden lg:table-cell" colSpan={2}>
{getEfficiencyBadge(
product.plannedQty,
product.actualQty,
product.defectQty,
)}
</TableCell>
<TableCell>
<span className="text-white font-semibold text-sm">
{formatCurrency(calculateProductTotal(product))}
</span>
</TableCell>
<TableCell colSpan={2}></TableCell>
</TableRow>
{/* Параметры товара */}
{isProductExpanded && (
<TableRow>
<TableCell colSpan={12} className="p-0">
<div className="bg-white/5 border-t border-white/10">
<div className="p-4">
<h4 className="text-white font-medium mb-3 flex items-center space-x-2">
<span className="text-xs text-white/60">
📋 Параметры товара:
</span>
</h4>
<div className="grid grid-cols-3 gap-4">
{product.parameters.map((param) => (
<div key={param.id} className="bg-white/5 rounded-lg p-3">
<div className="text-white/80 text-xs font-medium mb-1">
{param.name}
</div>
<div className="text-white text-sm">
{param.value} {param.unit || ''}
</div>
</div>
))}
</div>
</div>
</div>
</TableCell>
</TableRow>
)}
</React.Fragment>
)
})}
</React.Fragment>
)
})}
</React.Fragment>
)
})}
{/* Базовая детализация для поставок без маршрутов */}
{isSupplyExpanded && supply.items && !supply.routes && (
<TableRow>
<TableCell colSpan={12} className="bg-white/5 border-white/5">
<div className="p-4 space-y-4">
<h4 className="text-white font-medium">Детализация товаров:</h4>
<div className="grid gap-2">
{supply.items.map((item) => (
<div
key={item.id}
className="flex justify-between items-center py-2 px-3 bg-white/5 rounded-lg"
>
<div className="flex-1">
<span className="text-white text-sm">{item.name}</span>
{item.category && (
<span className="text-white/60 text-xs ml-2">({item.category})</span>
)}
</div>
<div className="flex items-center gap-4 text-sm">
<span className="text-white/80">{item.quantity} шт</span>
<span className="text-white/80">{formatCurrency(item.price)}</span>
<span className="text-white font-medium">
{formatCurrency(item.price * item.quantity)}
</span>
</div>
</div>
))}
</div>
</div>
</TableCell>
</TableRow>
)}
</React.Fragment>
)
})
)}
</TableBody>
</Table>
</Card>
</div>
)
}