Добавлен новый компонент для отображения бизнес-процессов в интерфейсе управления. Обновлен компонент UIKitSection для интеграции нового демо и улучшения навигации. Оптимизирована логика отображения данных и улучшена читаемость кода. Исправлены текстовые метки для повышения удобства использования.
This commit is contained in:
@ -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>
|
||||
|
Reference in New Issue
Block a user