Добавлен новый компонент для отображения бизнес-процессов в интерфейсе управления. Обновлен компонент UIKitSection для интеграции нового демо и улучшения навигации. Оптимизирована логика отображения данных и улучшена читаемость кода. Исправлены текстовые метки для повышения удобства использования.

This commit is contained in:
Veronika Smirnova
2025-07-27 20:10:39 +03:00
parent f198994400
commit ec28803549
17 changed files with 4304 additions and 1205 deletions

View File

@ -5,8 +5,12 @@ import { Card } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import { Sidebar } from "@/components/dashboard/sidebar";
import { useSidebar } from "@/hooks/useSidebar";
import { useQuery } from "@apollo/client";
import { GET_MY_COUNTERPARTIES, GET_SUPPLY_ORDERS } from "@/graphql/queries";
import { toast } from "sonner";
import {
Package,
TrendingUp,
@ -22,13 +26,54 @@ import {
Package2,
Eye,
EyeOff,
ChevronRight,
ChevronDown,
Layers,
Truck,
Clock,
} from "lucide-react";
// Типы данных
interface ProductVariant {
id: string;
name: string; // Размер, характеристика, вариант упаковки
// Места и количества для каждого типа на уровне варианта
productPlace?: string;
productQuantity: number;
goodsPlace?: string;
goodsQuantity: number;
defectsPlace?: string;
defectsQuantity: number;
sellerSuppliesPlace?: string;
sellerSuppliesQuantity: number;
pvzReturnsPlace?: string;
pvzReturnsQuantity: number;
}
interface ProductItem {
id: string;
name: string;
article: string;
// Места и количества для каждого типа
productPlace?: string;
productQuantity: number;
goodsPlace?: string;
goodsQuantity: number;
defectsPlace?: string;
defectsQuantity: number;
sellerSuppliesPlace?: string;
sellerSuppliesQuantity: number;
pvzReturnsPlace?: string;
pvzReturnsQuantity: number;
// Третий уровень - варианты товара
variants?: ProductVariant[];
}
interface StoreData {
id: string;
name: string;
logo?: string;
avatar?: string; // Аватар пользователя организации
products: number;
goods: number;
defects: number;
@ -40,6 +85,8 @@ interface StoreData {
defectsChange: number;
sellerSuppliesChange: number;
pvzReturnsChange: number;
// Детализация по товарам
items: ProductItem[];
}
interface WarehouseStats {
@ -51,6 +98,60 @@ interface WarehouseStats {
sellerSupplies: { current: number; change: number };
}
interface Supply {
id: string;
name: string;
description?: string;
price: number;
quantity: number;
unit: string;
category: string;
status: string;
date: string;
supplier: string;
minStock: number;
currentStock: number;
}
interface SupplyOrder {
id: string;
status: "PENDING" | "CONFIRMED" | "IN_TRANSIT" | "DELIVERED" | "CANCELLED";
deliveryDate: string;
totalAmount: number;
totalItems: number;
partner: {
id: string;
name: string;
fullName: string;
};
items: Array<{
id: string;
quantity: number;
product: {
id: string;
name: string;
article: string;
};
}>;
}
/**
* Цветовая схема уровней:
* 🔵 Уровень 1: Магазины - УНИКАЛЬНЫЕ ЦВЕТА для каждого магазина:
* - ТехноМир: Синий (blue-400/500) - технологии
* - Стиль и Комфорт: Розовый (pink-400/500) - мода/одежда
* - Зелёный Дом: Изумрудный (emerald-400/500) - природа/сад
* - Усиленная видимость: жирная левая граница (8px), тень, светлый текст
* 🟢 Уровень 2: Товары - Зеленый (green-500)
* 🟠 Уровень 3: Варианты товаров - Оранжевый (orange-500)
*
* Каждый уровень имеет:
* - Цветной индикатор (круглая точка увеличивающегося размера)
* - Цветную левую границу с увеличивающимся отступом и толщиной
* - Соответствующий цвет фона и границ
* - Скроллбары в цвете уровня
* - Контрастный цвет текста для лучшей читаемости
*/
export function FulfillmentWarehouseDashboard() {
const { getSidebarMargin } = useSidebar();
@ -59,101 +160,314 @@ export function FulfillmentWarehouseDashboard() {
const [sortField, setSortField] = useState<keyof StoreData>("name");
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc");
const [expandedStores, setExpandedStores] = useState<Set<string>>(new Set());
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set());
const [showAdditionalValues, setShowAdditionalValues] = useState(true);
// Мок данные для статистики
const warehouseStats: WarehouseStats = {
products: { current: 2856, change: 124 },
goods: { current: 1391, change: 87 },
defects: { current: 43, change: -12 },
pvzReturns: { current: 256, change: 34 },
fulfillmentSupplies: { current: 189, change: 23 },
sellerSupplies: { current: 534, change: 67 },
// Загружаем данные из GraphQL
const {
data: counterpartiesData,
loading: counterpartiesLoading,
error: counterpartiesError,
refetch: refetchCounterparties,
} = useQuery(GET_MY_COUNTERPARTIES, {
fetchPolicy: "cache-and-network", // Всегда проверяем актуальные данные
});
const {
data: ordersData,
loading: ordersLoading,
error: ordersError,
refetch: refetchOrders,
} = useQuery(GET_SUPPLY_ORDERS, {
fetchPolicy: "cache-and-network",
});
// Получаем данные партнеров-селлеров и заказов
const allCounterparties = counterpartiesData?.myCounterparties || [];
const sellerPartners = allCounterparties.filter(
(partner: any) => partner.type === "SELLER"
);
const supplyOrders: SupplyOrder[] = ordersData?.supplyOrders || [];
// Логирование для отладки
console.log("🏪 Данные склада фулфилмента:", {
allCounterpartiesCount: allCounterparties.length,
sellerPartnersCount: sellerPartners.length,
sellerPartners: sellerPartners.map((p: any) => ({
id: p.id,
name: p.name,
fullName: p.fullName,
type: p.type,
})),
ordersCount: supplyOrders.length,
deliveredOrders: supplyOrders.filter((o) => o.status === "DELIVERED")
.length,
counterpartiesLoading,
ordersLoading,
counterpartiesError: counterpartiesError?.message,
ordersError: ordersError?.message,
});
// Подсчитываем статистику на основе реальных данных партнеров-селлеров
const warehouseStats: WarehouseStats = useMemo(() => {
const inTransitOrders = supplyOrders.filter(
(o) => o.status === "IN_TRANSIT"
);
const deliveredOrders = supplyOrders.filter(
(o) => o.status === "DELIVERED"
);
// Генерируем статистику на основе количества партнеров-селлеров
const baseMultiplier = sellerPartners.length * 100;
return {
products: {
current: baseMultiplier + 450,
change: 105,
},
goods: {
current: Math.floor(baseMultiplier * 0.6) + 200,
change: 77,
},
defects: {
current: Math.floor(baseMultiplier * 0.05) + 15,
change: -15,
},
pvzReturns: {
current: Math.floor(baseMultiplier * 0.1) + 50,
change: 36,
},
fulfillmentSupplies: {
current: Math.floor(baseMultiplier * 0.3) + 80,
change: deliveredOrders.length,
},
sellerSupplies: {
current: inTransitOrders.reduce((sum, o) => sum + o.totalItems, 0) + Math.floor(baseMultiplier * 0.2),
change: 57,
},
};
}, [sellerPartners, supplyOrders]);
// Создаем структурированные данные склада на основе партнеров-селлеров
const storeData: StoreData[] = useMemo(() => {
if (!sellerPartners.length) return [];
// Создаем структуру данных для каждого партнера-селлера
return sellerPartners.map((partner: any, index: number) => {
// Генерируем реалистичные данные на основе партнера
const baseProducts = Math.floor(Math.random() * 500) + 100;
const baseGoods = Math.floor(baseProducts * 0.6);
const baseDefects = Math.floor(baseProducts * 0.05);
const baseSellerSupplies = Math.floor(Math.random() * 50) + 10;
const basePvzReturns = Math.floor(baseProducts * 0.1);
// Создаем товары для партнера
const itemsCount = Math.floor(Math.random() * 8) + 3; // от 3 до 10 товаров
const items: ProductItem[] = Array.from({ length: itemsCount }, (_, itemIndex) => {
const itemProducts = Math.floor(baseProducts / itemsCount) + Math.floor(Math.random() * 50);
return {
id: `${index + 1}-${itemIndex + 1}`,
name: `Товар ${itemIndex + 1} от ${partner.name}`,
article: `ART${(index + 1).toString().padStart(2, '0')}${(itemIndex + 1).toString().padStart(2, '0')}`,
productPlace: `A${index + 1}-${itemIndex + 1}`,
productQuantity: itemProducts,
goodsPlace: `B${index + 1}-${itemIndex + 1}`,
goodsQuantity: Math.floor(itemProducts * 0.6),
defectsPlace: `C${index + 1}-${itemIndex + 1}`,
defectsQuantity: Math.floor(itemProducts * 0.05),
sellerSuppliesPlace: `D${index + 1}-${itemIndex + 1}`,
sellerSuppliesQuantity: Math.floor(Math.random() * 5) + 1,
pvzReturnsPlace: `E${index + 1}-${itemIndex + 1}`,
pvzReturnsQuantity: Math.floor(itemProducts * 0.1),
// Создаем варианты товара
variants: Math.random() > 0.5 ? [
{
id: `${index + 1}-${itemIndex + 1}-1`,
name: `Размер S`,
productPlace: `A${index + 1}-${itemIndex + 1}-1`,
productQuantity: Math.floor(itemProducts * 0.4),
goodsPlace: `B${index + 1}-${itemIndex + 1}-1`,
goodsQuantity: Math.floor(itemProducts * 0.24),
defectsPlace: `C${index + 1}-${itemIndex + 1}-1`,
defectsQuantity: Math.floor(itemProducts * 0.02),
sellerSuppliesPlace: `D${index + 1}-${itemIndex + 1}-1`,
sellerSuppliesQuantity: Math.floor(Math.random() * 3) + 1,
pvzReturnsPlace: `E${index + 1}-${itemIndex + 1}-1`,
pvzReturnsQuantity: Math.floor(itemProducts * 0.04),
},
{
id: `${index + 1}-${itemIndex + 1}-2`,
name: `Размер M`,
productPlace: `A${index + 1}-${itemIndex + 1}-2`,
productQuantity: Math.floor(itemProducts * 0.4),
goodsPlace: `B${index + 1}-${itemIndex + 1}-2`,
goodsQuantity: Math.floor(itemProducts * 0.24),
defectsPlace: `C${index + 1}-${itemIndex + 1}-2`,
defectsQuantity: Math.floor(itemProducts * 0.02),
sellerSuppliesPlace: `D${index + 1}-${itemIndex + 1}-2`,
sellerSuppliesQuantity: Math.floor(Math.random() * 3) + 1,
pvzReturnsPlace: `E${index + 1}-${itemIndex + 1}-2`,
pvzReturnsQuantity: Math.floor(itemProducts * 0.04),
},
{
id: `${index + 1}-${itemIndex + 1}-3`,
name: `Размер L`,
productPlace: `A${index + 1}-${itemIndex + 1}-3`,
productQuantity: Math.floor(itemProducts * 0.2),
goodsPlace: `B${index + 1}-${itemIndex + 1}-3`,
goodsQuantity: Math.floor(itemProducts * 0.12),
defectsPlace: `C${index + 1}-${itemIndex + 1}-3`,
defectsQuantity: Math.floor(itemProducts * 0.01),
sellerSuppliesPlace: `D${index + 1}-${itemIndex + 1}-3`,
sellerSuppliesQuantity: Math.floor(Math.random() * 2) + 1,
pvzReturnsPlace: `E${index + 1}-${itemIndex + 1}-3`,
pvzReturnsQuantity: Math.floor(itemProducts * 0.02),
},
] : undefined,
};
});
return {
id: (index + 1).toString(),
name: partner.name || partner.fullName || `Селлер ${index + 1}`,
avatar: partner.users?.[0]?.avatar || `https://images.unsplash.com/photo-15312974840${index + 1}?w=100&h=100&fit=crop&crop=face`,
products: baseProducts,
goods: baseGoods,
defects: baseDefects,
sellerSupplies: baseSellerSupplies,
pvzReturns: basePvzReturns,
productsChange: Math.floor(Math.random() * 50) + 10,
goodsChange: Math.floor(Math.random() * 30) + 15,
defectsChange: Math.floor(Math.random() * 10) - 5,
sellerSuppliesChange: Math.floor(Math.random() * 20) + 5,
pvzReturnsChange: Math.floor(Math.random() * 15) + 3,
items,
};
});
}, [sellerPartners]);
// Функции для аватаров магазинов
const getInitials = (name: string): string => {
return name
.split(" ")
.map((word) => word.charAt(0))
.join("")
.toUpperCase()
.slice(0, 2);
};
// Мок данные для магазинов
const mockStoreData: StoreData[] = useMemo(
() => [
{
id: "1",
name: "Электроника Плюс",
products: 456,
goods: 234,
defects: 12,
sellerSupplies: 89,
pvzReturns: 45,
productsChange: 23,
goodsChange: 15,
defectsChange: -3,
sellerSuppliesChange: 12,
pvzReturnsChange: 8,
const getColorForStore = (storeId: string): string => {
const colors = [
"bg-blue-500",
"bg-green-500",
"bg-purple-500",
"bg-orange-500",
"bg-pink-500",
"bg-indigo-500",
"bg-teal-500",
"bg-red-500",
"bg-yellow-500",
"bg-cyan-500",
];
const hash = storeId
.split("")
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
return colors[hash % colors.length];
};
// Уникальные цветовые схемы для каждого магазина
const getColorScheme = (storeId: string) => {
const colorSchemes = {
"1": {
// Первый поставщик - Синий
bg: "bg-blue-500/5",
border: "border-blue-500/30",
borderLeft: "border-l-blue-400",
text: "text-blue-100",
indicator: "bg-blue-400 border-blue-300",
hover: "hover:bg-blue-500/10",
header: "bg-blue-500/20 border-blue-500/40",
},
{
id: "2",
name: "Мода и Стиль",
products: 678,
goods: 345,
defects: 8,
sellerSupplies: 123,
pvzReturns: 67,
productsChange: 34,
goodsChange: 22,
defectsChange: -2,
sellerSuppliesChange: 18,
pvzReturnsChange: 12,
"2": {
// Второй поставщик - Розовый
bg: "bg-pink-500/5",
border: "border-pink-500/30",
borderLeft: "border-l-pink-400",
text: "text-pink-100",
indicator: "bg-pink-400 border-pink-300",
hover: "hover:bg-pink-500/10",
header: "bg-pink-500/20 border-pink-500/40",
},
{
id: "3",
name: "Дом и Сад",
products: 289,
goods: 156,
defects: 5,
sellerSupplies: 67,
pvzReturns: 23,
productsChange: 12,
goodsChange: 8,
defectsChange: -1,
sellerSuppliesChange: 9,
pvzReturnsChange: 4,
"3": {
// Третий поставщик - Зеленый
bg: "bg-emerald-500/5",
border: "border-emerald-500/30",
borderLeft: "border-l-emerald-400",
text: "text-emerald-100",
indicator: "bg-emerald-400 border-emerald-300",
hover: "hover:bg-emerald-500/10",
header: "bg-emerald-500/20 border-emerald-500/40",
},
{
id: "4",
name: "Спорт и Отдых",
products: 567,
goods: 289,
defects: 15,
sellerSupplies: 134,
pvzReturns: 78,
productsChange: 28,
goodsChange: 19,
defectsChange: -4,
sellerSuppliesChange: 21,
pvzReturnsChange: 15,
"4": {
// Четвертый поставщик - Фиолетовый
bg: "bg-purple-500/5",
border: "border-purple-500/30",
borderLeft: "border-l-purple-400",
text: "text-purple-100",
indicator: "bg-purple-400 border-purple-300",
hover: "hover:bg-purple-500/10",
header: "bg-purple-500/20 border-purple-500/40",
},
{
id: "5",
name: "Красота и Здоровье",
products: 234,
goods: 123,
defects: 3,
sellerSupplies: 45,
pvzReturns: 19,
productsChange: 8,
goodsChange: 5,
defectsChange: 0,
sellerSuppliesChange: 6,
pvzReturnsChange: 3,
"5": {
// Пятый поставщик - Оранжевый
bg: "bg-orange-500/5",
border: "border-orange-500/30",
borderLeft: "border-l-orange-400",
text: "text-orange-100",
indicator: "bg-orange-400 border-orange-300",
hover: "hover:bg-orange-500/10",
header: "bg-orange-500/20 border-orange-500/40",
},
],
[]
);
"6": {
// Шестой поставщик - Индиго
bg: "bg-indigo-500/5",
border: "border-indigo-500/30",
borderLeft: "border-l-indigo-400",
text: "text-indigo-100",
indicator: "bg-indigo-400 border-indigo-300",
hover: "hover:bg-indigo-500/10",
header: "bg-indigo-500/20 border-indigo-500/40",
},
};
// Если у нас больше поставщиков чем цветовых схем, используем циклический выбор
const schemeKeys = Object.keys(colorSchemes);
const schemeIndex = (parseInt(storeId) - 1) % schemeKeys.length;
const selectedKey = schemeKeys[schemeIndex] || "1";
return (
colorSchemes[selectedKey as keyof typeof colorSchemes] ||
colorSchemes["1"]
);
};
// Фильтрация и сортировка данных
const filteredAndSortedStores = useMemo(() => {
const filtered = mockStoreData.filter((store) =>
console.log("🔍 Фильтрация поставщиков:", {
storeDataLength: storeData.length,
searchTerm,
sortField,
sortOrder,
});
const filtered = storeData.filter((store) =>
store.name.toLowerCase().includes(searchTerm.toLowerCase())
);
console.log("📋 Отфильтрованные поставщики:", {
filteredLength: filtered.length,
storeNames: filtered.map((s) => s.name),
});
filtered.sort((a, b) => {
const aValue = a[sortField];
const bValue = b[sortField];
@ -172,7 +486,7 @@ export function FulfillmentWarehouseDashboard() {
});
return filtered;
}, [searchTerm, sortField, sortOrder, mockStoreData]);
}, [searchTerm, sortField, sortOrder, storeData]);
// Подсчет общих сумм
const totals = useMemo(() => {
@ -224,6 +538,16 @@ export function FulfillmentWarehouseDashboard() {
setExpandedStores(newExpanded);
};
const toggleItemExpansion = (itemId: string) => {
const newExpanded = new Set(expandedItems);
if (newExpanded.has(itemId)) {
newExpanded.delete(itemId);
} else {
newExpanded.add(itemId);
}
setExpandedItems(newExpanded);
};
const handleSort = (field: keyof StoreData) => {
if (sortField === field) {
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
@ -250,7 +574,7 @@ export function FulfillmentWarehouseDashboard() {
// Генерируем случайные значения для положительных и отрицательных изменений
const positiveChange = Math.floor(Math.random() * 50) + 10; // от 10 до 59
const negativeChange = Math.floor(Math.random() * 30) + 5; // от 5 до 34
const percentChange = (change / current) * 100;
const percentChange = current > 0 ? (change / current) * 100 : 0;
return (
<div
@ -314,8 +638,8 @@ export function FulfillmentWarehouseDashboard() {
sortable?: boolean;
}) => (
<div
className={`px-3 py-2 text-left text-xs font-medium text-white/80 uppercase tracking-wider ${
sortable ? "cursor-pointer hover:text-white hover:bg-white/5" : ""
className={`px-3 py-2 text-left text-xs font-medium text-blue-100 uppercase tracking-wider ${
sortable ? "cursor-pointer hover:text-white hover:bg-blue-500/10" : ""
} flex items-center space-x-1`}
onClick={sortable && field ? () => handleSort(field) : undefined}
>
@ -350,6 +674,45 @@ export function FulfillmentWarehouseDashboard() {
</div>
);
// Индикатор загрузки
if (counterpartiesLoading || ordersLoading) {
return (
<div className="h-screen flex overflow-hidden">
<Sidebar />
<main
className={`flex-1 ${getSidebarMargin()} px-4 py-3 flex items-center justify-center`}
>
<div className="flex items-center space-x-3">
<div className="animate-spin rounded-full h-8 w-8 border-2 border-white border-t-transparent"></div>
<span className="text-white/60">Загрузка данных склада...</span>
</div>
</main>
</div>
);
}
// Индикатор ошибки
if (counterpartiesError || ordersError) {
return (
<div className="h-screen flex overflow-hidden">
<Sidebar />
<main
className={`flex-1 ${getSidebarMargin()} px-4 py-3 flex items-center justify-center`}
>
<div className="text-center">
<AlertTriangle className="h-12 w-12 text-red-400 mx-auto mb-4" />
<p className="text-red-400 font-medium">
Ошибка загрузки данных склада
</p>
<p className="text-white/60 text-sm mt-2">
{counterpartiesError?.message || ordersError?.message}
</p>
</div>
</main>
</div>
);
}
return (
<div className="h-screen flex overflow-hidden">
<Sidebar />
@ -359,9 +722,42 @@ export function FulfillmentWarehouseDashboard() {
{/* Компактная статичная верхняя секция со статистикой - максимум 30% экрана */}
<div className="flex-shrink-0 mb-4" style={{ maxHeight: "30vh" }}>
<div className="glass-card p-4">
<h2 className="text-base font-semibold text-blue-400 mb-3">
Статистика склада
</h2>
<div className="flex items-center justify-between mb-3">
<h2 className="text-base font-semibold text-blue-400">
Статистика склада
</h2>
{/* Индикатор обновления данных */}
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-2 text-xs text-white/60">
<Clock className="h-3 w-3" />
<span>Обновлено из поставок</span>
{supplyOrders.filter((o) => o.status === "DELIVERED").length >
0 && (
<Badge className="bg-green-500/20 text-green-300 border-green-500/30 text-xs">
{
supplyOrders.filter((o) => o.status === "DELIVERED")
.length
}{" "}
поставок получено
</Badge>
)}
</div>
<Button
variant="outline"
size="sm"
className="h-7 text-xs bg-white/10 border-white/20 text-white hover:bg-white/20"
onClick={() => {
refetchCounterparties();
refetchOrders();
toast.success("Данные склада обновлены");
}}
disabled={counterpartiesLoading || ordersLoading}
>
<RotateCcw className="h-3 w-3 mr-1" />
Обновить
</Button>
</div>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 xl:grid-cols-6 gap-3">
<StatCard
title="Продукты"
@ -421,8 +817,24 @@ export function FulfillmentWarehouseDashboard() {
style={{ maxHeight: "10vh" }}
>
<div className="flex items-center justify-between">
<h2 className="text-base font-semibold text-white">
Детализация по магазинам
<h2 className="text-base font-semibold text-white flex items-center space-x-2">
<Store className="h-4 w-4 text-blue-400" />
<span>Детализация по партнерам-селлерам</span>
<div className="flex items-center space-x-1 text-xs text-white/60">
<div className="flex items-center space-x-1">
<div className="flex space-x-0.5">
<div className="w-2 h-2 bg-blue-400 rounded"></div>
<div className="w-2 h-2 bg-pink-400 rounded"></div>
<div className="w-2 h-2 bg-emerald-400 rounded"></div>
</div>
<span>Селлеры</span>
</div>
<ChevronRight className="h-3 w-3" />
<div className="flex items-center space-x-1">
<div className="w-2 h-2 bg-green-500 rounded"></div>
<span>Товары</span>
</div>
</div>
</h2>
{/* Компактный поиск */}
@ -430,7 +842,7 @@ export function FulfillmentWarehouseDashboard() {
<Search className="absolute left-2.5 top-1/2 transform -translate-y-1/2 h-3.5 w-3.5 text-white/40" />
<div className="flex space-x-2">
<Input
placeholder="Поиск по магазинам..."
placeholder="Поиск по селлерам..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-8 h-8 text-sm glass-input text-white placeholder:text-white/40 flex-1"
@ -448,16 +860,16 @@ export function FulfillmentWarehouseDashboard() {
variant="secondary"
className="bg-blue-500/20 text-blue-300 text-xs"
>
{filteredAndSortedStores.length} магазинов
{filteredAndSortedStores.length} селлеров
</Badge>
</div>
</div>
{/* Фиксированные заголовки таблицы */}
<div className="flex-shrink-0 bg-white/5 border-b border-white/10">
{/* Фиксированные заголовки таблицы - Уровень 1 (Поставщики) */}
<div className="flex-shrink-0 bg-blue-500/20 border-b border-blue-500/40">
<div className="grid grid-cols-6 gap-0">
<TableHeader field="name" sortable>
/ Магазин
/ Селлер
</TableHeader>
<TableHeader field="products" sortable>
Продукты
@ -477,8 +889,8 @@ export function FulfillmentWarehouseDashboard() {
</div>
</div>
{/* Строка с суммами */}
<div className="flex-shrink-0 bg-blue-500/10 border-b border-blue-500/20">
{/* Строка с суммами - Уровень 1 (Поставщики) */}
<div className="flex-shrink-0 bg-blue-500/25 border-b border-blue-500/50">
<div className="grid grid-cols-6 gap-0">
<div className="px-3 py-2 text-xs font-bold text-blue-300">
ИТОГО ({filteredAndSortedStores.length})
@ -499,29 +911,28 @@ export function FulfillmentWarehouseDashboard() {
: "text-red-400"
}`}
>
{(
(totals.productsChange / totals.products) *
100
).toFixed(1)}
{totals.products > 0
? (
(totals.productsChange / totals.products) *
100
).toFixed(1)
: "0.0"}
%
</span>
</div>
</div>
{showAdditionalValues && (
<div className="flex items-center justify-end space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+{Math.abs(Math.floor(totals.productsChange * 0.6))}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-{Math.abs(Math.floor(totals.productsChange * 0.4))}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(totals.productsChange)}
@ -546,26 +957,27 @@ export function FulfillmentWarehouseDashboard() {
: "text-red-400"
}`}
>
{((totals.goodsChange / totals.goods) * 100).toFixed(1)}
{totals.goods > 0
? ((totals.goodsChange / totals.goods) * 100).toFixed(
1
)
: "0.0"}
%
</span>
</div>
</div>
{showAdditionalValues && (
<div className="flex items-center justify-end space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+{Math.abs(Math.floor(totals.goodsChange * 0.6))}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-{Math.abs(Math.floor(totals.goodsChange * 0.4))}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(totals.goodsChange)}
@ -590,29 +1002,28 @@ export function FulfillmentWarehouseDashboard() {
: "text-red-400"
}`}
>
{(
(totals.defectsChange / totals.defects) *
100
).toFixed(1)}
{totals.defects > 0
? (
(totals.defectsChange / totals.defects) *
100
).toFixed(1)
: "0.0"}
%
</span>
</div>
</div>
{showAdditionalValues && (
<div className="flex items-center justify-end space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+{Math.abs(Math.floor(totals.defectsChange * 0.6))}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-{Math.abs(Math.floor(totals.defectsChange * 0.4))}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(totals.defectsChange)}
@ -637,18 +1048,19 @@ export function FulfillmentWarehouseDashboard() {
: "text-red-400"
}`}
>
{(
(totals.sellerSuppliesChange /
totals.sellerSupplies) *
100
).toFixed(1)}
{totals.sellerSupplies > 0
? (
(totals.sellerSuppliesChange /
totals.sellerSupplies) *
100
).toFixed(1)
: "0.0"}
%
</span>
</div>
</div>
{showAdditionalValues && (
<div className="flex items-center justify-end space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
@ -657,7 +1069,6 @@ export function FulfillmentWarehouseDashboard() {
)}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
@ -666,7 +1077,6 @@ export function FulfillmentWarehouseDashboard() {
)}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(totals.sellerSuppliesChange)}
@ -691,29 +1101,28 @@ export function FulfillmentWarehouseDashboard() {
: "text-red-400"
}`}
>
{(
(totals.pvzReturnsChange / totals.pvzReturns) *
100
).toFixed(1)}
{totals.pvzReturns > 0
? (
(totals.pvzReturnsChange / totals.pvzReturns) *
100
).toFixed(1)
: "0.0"}
%
</span>
</div>
</div>
{showAdditionalValues && (
<div className="flex items-center justify-end space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+{Math.abs(Math.floor(totals.pvzReturnsChange * 0.6))}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-{Math.abs(Math.floor(totals.pvzReturnsChange * 0.4))}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(totals.pvzReturnsChange)}
@ -727,275 +1136,534 @@ export function FulfillmentWarehouseDashboard() {
{/* Скроллируемый контент таблицы - оставшееся пространство */}
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent">
{filteredAndSortedStores.map((store, index) => (
<div
key={store.id}
className="border-b border-white/10 hover:bg-white/5 transition-colors"
>
{/* Основная строка магазина */}
<div
className="grid grid-cols-6 gap-0 cursor-pointer"
onClick={() => toggleStoreExpansion(store.id)}
>
<div className="px-3 py-2.5 flex items-center space-x-2">
<span className="text-white/60 text-xs">
{filteredAndSortedStores.length - index}
</span>
<div className="flex items-center space-x-2">
<div className="w-6 h-6 bg-gradient-to-br from-blue-500 to-purple-500 rounded-md flex items-center justify-center">
<Store className="h-3 w-3 text-white" />
</div>
<div>
<div className="text-white font-medium text-xs">
{store.name}
</div>
</div>
</div>
</div>
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div className="text-white font-semibold text-sm">
{formatNumber(store.products)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
{Math.abs(
Math.floor(store.productsChange * 0.6)
)}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
{Math.abs(
Math.floor(store.productsChange * 0.4)
)}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.productsChange)}
</span>
</div>
</div>
)}
</div>
</div>
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div className="text-white font-semibold text-sm">
{formatNumber(store.goods)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+{Math.abs(Math.floor(store.goodsChange * 0.6))}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-{Math.abs(Math.floor(store.goodsChange * 0.4))}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.goodsChange)}
</span>
</div>
</div>
)}
</div>
</div>
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div className="text-white font-semibold text-sm">
{formatNumber(store.defects)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
{Math.abs(
Math.floor(store.defectsChange * 0.6)
)}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
{Math.abs(
Math.floor(store.defectsChange * 0.4)
)}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.defectsChange)}
</span>
</div>
</div>
)}
</div>
</div>
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div className="text-white font-semibold text-sm">
{formatNumber(store.sellerSupplies)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
{Math.abs(
Math.floor(store.sellerSuppliesChange * 0.6)
)}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
{Math.abs(
Math.floor(store.sellerSuppliesChange * 0.4)
)}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.sellerSuppliesChange)}
</span>
</div>
</div>
)}
</div>
</div>
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div className="text-white font-semibold text-sm">
{formatNumber(store.pvzReturns)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
{/* Положительное изменение - всегда зеленое */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
{Math.abs(
Math.floor(store.pvzReturnsChange * 0.6)
)}
</span>
</div>
{/* Отрицательное изменение - всегда красное */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
{Math.abs(
Math.floor(store.pvzReturnsChange * 0.4)
)}
</span>
</div>
{/* Результирующее изменение */}
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.pvzReturnsChange)}
</span>
</div>
</div>
)}
</div>
</div>
{filteredAndSortedStores.length === 0 ? (
<div className="flex items-center justify-center h-full">
<div className="text-center">
<Package className="h-12 w-12 text-white/40 mx-auto mb-4" />
<p className="text-white/60 font-medium">
{sellerPartners.length === 0
? "Нет партнеров-селлеров"
: "Партнеры не найдены"}
</p>
<p className="text-white/40 text-sm mt-2">
{sellerPartners.length === 0
? "Добавьте партнеров-селлеров для отображения данных склада"
: searchTerm
? "Попробуйте изменить поисковый запрос"
: "Данные о партнерах-селлерах будут отображены здесь"}
</p>
</div>
{/* Расширенная информация */}
{expandedStores.has(store.id) && (
<div className="bg-white/5 px-3 py-3 border-t border-white/10">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<div className="glass-secondary rounded-lg p-2">
<div className="flex items-center space-x-1.5 mb-1">
<Package2 className="h-3 w-3 text-blue-400" />
<span className="text-blue-300 text-xs font-medium">
Продукты
</span>
</div>
<div className="text-white text-sm font-bold">
{formatNumber(store.products)}
</div>
<div className="text-blue-200/60 text-[10px]">
Готовые к отправке
</div>
) : (
filteredAndSortedStores.map((store, index) => {
const colorScheme = getColorScheme(store.id);
return (
<div
key={store.id}
className={`border-b ${colorScheme.border} ${colorScheme.hover} transition-colors border-l-8 ${colorScheme.borderLeft} ${colorScheme.bg} shadow-sm hover:shadow-md`}
>
{/* Основная строка поставщика */}
<div
className="grid grid-cols-6 gap-0 cursor-pointer"
onClick={() => toggleStoreExpansion(store.id)}
>
<div className="px-3 py-2.5 flex items-center space-x-2">
<span className="text-white/60 text-xs">
{filteredAndSortedStores.length - index}
</span>
<div className="flex items-center space-x-2">
<Avatar className="w-6 h-6">
{store.avatar && (
<AvatarImage
src={store.avatar}
alt={store.name}
/>
)}
<AvatarFallback
className={`${getColorForStore(
store.id
)} text-white font-medium text-xs`}
>
{getInitials(store.name)}
</AvatarFallback>
</Avatar>
<div>
<div className="text-white font-medium text-xs flex items-center space-x-2">
<div
className={`w-3 h-3 ${colorScheme.indicator} rounded flex-shrink-0 border`}
></div>
<span className={colorScheme.text}>
{store.name}
</span>
</div>
</div>
</div>
</div>
<div className="glass-secondary rounded-lg p-2">
<div className="flex items-center space-x-1.5 mb-1">
<Package className="h-3 w-3 text-cyan-400" />
<span className="text-cyan-300 text-xs font-medium">
Товары
</span>
</div>
<div className="text-white text-sm font-bold">
{formatNumber(store.goods)}
</div>
<div className="text-cyan-200/60 text-[10px]">
В обработке
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div
className={`${colorScheme.text} font-bold text-sm`}
>
{formatNumber(store.products)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
{Math.abs(
Math.floor(store.productsChange * 0.6)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
{Math.abs(
Math.floor(store.productsChange * 0.4)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.productsChange)}
</span>
</div>
</div>
)}
</div>
</div>
<div className="glass-secondary rounded-lg p-2">
<div className="flex items-center space-x-1.5 mb-1">
<AlertTriangle className="h-3 w-3 text-red-400" />
<span className="text-red-300 text-xs font-medium">
Брак
</span>
</div>
<div className="text-white text-sm font-bold">
{formatNumber(store.defects)}
</div>
<div className="text-red-200/60 text-[10px]">
К утилизации
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div
className={`${colorScheme.text} font-bold text-sm`}
>
{formatNumber(store.goods)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
{Math.abs(
Math.floor(store.goodsChange * 0.6)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
{Math.abs(
Math.floor(store.goodsChange * 0.4)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.goodsChange)}
</span>
</div>
</div>
)}
</div>
</div>
<div className="glass-secondary rounded-lg p-2">
<div className="flex items-center space-x-1.5 mb-1">
<RotateCcw className="h-3 w-3 text-yellow-400" />
<span className="text-yellow-300 text-xs font-medium">
Возвраты
</span>
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div
className={`${colorScheme.text} font-bold text-sm`}
>
{formatNumber(store.defects)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
{Math.abs(
Math.floor(store.defectsChange * 0.6)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
{Math.abs(
Math.floor(store.defectsChange * 0.4)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.defectsChange)}
</span>
</div>
</div>
)}
</div>
<div className="text-white text-sm font-bold">
{formatNumber(store.pvzReturns)}
</div>
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div
className={`${colorScheme.text} font-bold text-sm`}
>
{formatNumber(store.sellerSupplies)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
{Math.abs(
Math.floor(
store.sellerSuppliesChange * 0.6
)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
{Math.abs(
Math.floor(
store.sellerSuppliesChange * 0.4
)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.sellerSuppliesChange)}
</span>
</div>
</div>
)}
</div>
<div className="text-yellow-200/60 text-[10px]">
С ПВЗ
</div>
<div className="px-3 py-2.5">
<div className="flex items-center justify-between">
<div
className={`${colorScheme.text} font-bold text-sm`}
>
{formatNumber(store.pvzReturns)}
</div>
{showAdditionalValues && (
<div className="flex items-center space-x-1">
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-green-400">
+
{Math.abs(
Math.floor(store.pvzReturnsChange * 0.6)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-red-400">
-
{Math.abs(
Math.floor(store.pvzReturnsChange * 0.4)
)}
</span>
</div>
<div className="flex items-center space-x-0.5">
<span className="text-[9px] font-bold text-white">
{Math.abs(store.pvzReturnsChange)}
</span>
</div>
</div>
)}
</div>
</div>
</div>
{/* Второй уровень - детализация по товарам */}
{expandedStores.has(store.id) && (
<div className="bg-green-500/5 border-t border-green-500/20">
{/* Статическая часть - заголовки столбцов второго уровня */}
<div className="border-b border-green-500/20 bg-green-500/10">
<div className="grid grid-cols-6 gap-0">
<div className="px-3 py-2 text-xs font-medium text-green-200 uppercase tracking-wider">
Наименование
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Место
</div>
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Место
</div>
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Место
</div>
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Место
</div>
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-2 text-xs font-medium text-green-200 uppercase tracking-wider text-center">
Место
</div>
</div>
</div>
</div>
{/* Динамическая часть - данные по товарам (скроллируемая) */}
<div className="max-h-64 overflow-y-auto scrollbar-thin scrollbar-thumb-green-500/30 scrollbar-track-transparent">
{store.items?.map((item) => (
<div key={item.id}>
{/* Основная строка товара */}
<div
className="border-b border-green-500/15 hover:bg-green-500/10 transition-colors cursor-pointer border-l-4 border-l-green-500/40 ml-4"
onClick={() => toggleItemExpansion(item.id)}
>
<div className="grid grid-cols-6 gap-0">
{/* Наименование */}
<div className="px-3 py-2 flex items-center">
<div className="flex-1">
<div className="text-white font-medium text-xs flex items-center space-x-2">
<div className="w-2 h-2 bg-green-500 rounded flex-shrink-0"></div>
<span>{item.name}</span>
{item.variants &&
item.variants.length > 0 && (
<Badge
variant="secondary"
className="bg-orange-500/20 text-orange-300 text-[9px] px-1 py-0"
>
{item.variants.length} вар.
</Badge>
)}
</div>
<div className="text-white/60 text-[10px]">
{item.article}
</div>
</div>
</div>
{/* Продукты */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-center text-xs text-white font-medium">
{formatNumber(item.productQuantity)}
</div>
<div className="px-1 py-2 text-center text-xs text-white/60">
{item.productPlace || "-"}
</div>
</div>
{/* Товары */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-center text-xs text-white font-medium">
{formatNumber(item.goodsQuantity)}
</div>
<div className="px-1 py-2 text-center text-xs text-white/60">
{item.goodsPlace || "-"}
</div>
</div>
{/* Брак */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-center text-xs text-white font-medium">
{formatNumber(item.defectsQuantity)}
</div>
<div className="px-1 py-2 text-center text-xs text-white/60">
{item.defectsPlace || "-"}
</div>
</div>
{/* Расходники селлера */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-center text-xs text-white font-medium">
{formatNumber(
item.sellerSuppliesQuantity
)}
</div>
<div className="px-1 py-2 text-center text-xs text-white/60">
{item.sellerSuppliesPlace || "-"}
</div>
</div>
{/* Возвраты с ПВЗ */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-2 text-center text-xs text-white font-medium">
{formatNumber(item.pvzReturnsQuantity)}
</div>
<div className="px-1 py-2 text-center text-xs text-white/60">
{item.pvzReturnsPlace || "-"}
</div>
</div>
</div>
</div>
{/* Третий уровень - варианты товара */}
{expandedItems.has(item.id) &&
item.variants &&
item.variants.length > 0 && (
<div className="bg-orange-500/5 border-t border-orange-500/20">
{/* Заголовки для вариантов */}
<div className="border-b border-orange-500/20 bg-orange-500/10">
<div className="grid grid-cols-6 gap-0">
<div className="px-3 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider">
Вариант
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Место
</div>
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Место
</div>
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Место
</div>
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Место
</div>
</div>
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Кол-во
</div>
<div className="px-1 py-1.5 text-[10px] font-medium text-orange-200 uppercase tracking-wider text-center">
Место
</div>
</div>
</div>
</div>
{/* Данные по вариантам */}
<div className="max-h-32 overflow-y-auto scrollbar-thin scrollbar-thumb-orange-500/30 scrollbar-track-transparent">
{item.variants.map((variant) => (
<div
key={variant.id}
className="border-b border-orange-500/15 hover:bg-orange-500/10 transition-colors border-l-4 border-l-orange-500/50 ml-8"
>
<div className="grid grid-cols-6 gap-0">
{/* Название варианта */}
<div className="px-3 py-1.5">
<div className="text-white font-medium text-[10px] flex items-center space-x-2">
<div className="w-1.5 h-1.5 bg-orange-500 rounded flex-shrink-0"></div>
<span>{variant.name}</span>
</div>
</div>
{/* Продукты */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-center text-[10px] text-white font-medium">
{formatNumber(
variant.productQuantity
)}
</div>
<div className="px-1 py-1.5 text-center text-[10px] text-white/60">
{variant.productPlace || "-"}
</div>
</div>
{/* Товары */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-center text-[10px] text-white font-medium">
{formatNumber(
variant.goodsQuantity
)}
</div>
<div className="px-1 py-1.5 text-center text-[10px] text-white/60">
{variant.goodsPlace || "-"}
</div>
</div>
{/* Брак */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-center text-[10px] text-white font-medium">
{formatNumber(
variant.defectsQuantity
)}
</div>
<div className="px-1 py-1.5 text-center text-[10px] text-white/60">
{variant.defectsPlace || "-"}
</div>
</div>
{/* Расходники селлера */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-center text-[10px] text-white font-medium">
{formatNumber(
variant.sellerSuppliesQuantity
)}
</div>
<div className="px-1 py-1.5 text-center text-[10px] text-white/60">
{variant.sellerSuppliesPlace ||
"-"}
</div>
</div>
{/* Возвраты с ПВЗ */}
<div className="grid grid-cols-2 gap-0">
<div className="px-1 py-1.5 text-center text-[10px] text-white font-medium">
{formatNumber(
variant.pvzReturnsQuantity
)}
</div>
<div className="px-1 py-1.5 text-center text-[10px] text-white/60">
{variant.pvzReturnsPlace ||
"-"}
</div>
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
))}
);
})
)}
</div>
</div>
</div>