Добавлен новый компонент FulfillmentWarehouseDemo и соответствующая вкладка в UIKitSection. Обновлены импорты и форматирование кода для улучшения читаемости и структуры. Исправлены стили вкладок для более удобного взаимодействия с пользователем.
This commit is contained in:
332
src/components/admin/ui-kit/fulfillment-warehouse-demo.tsx
Normal file
332
src/components/admin/ui-kit/fulfillment-warehouse-demo.tsx
Normal file
@ -0,0 +1,332 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { ChevronUp, ChevronDown, TrendingUp, TrendingDown } from "lucide-react";
|
||||
|
||||
interface TableColumn {
|
||||
key: string;
|
||||
title: string;
|
||||
sortable: boolean;
|
||||
}
|
||||
|
||||
interface TableData {
|
||||
id: number;
|
||||
number: string;
|
||||
store: string;
|
||||
product: string;
|
||||
goods: number;
|
||||
defects: number;
|
||||
sellerSupplies: number;
|
||||
pvzReturns: number;
|
||||
}
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{ key: "number", title: "№", sortable: true },
|
||||
{ key: "store", title: "Магазин", sortable: true },
|
||||
{ key: "product", title: "Продукт", sortable: true },
|
||||
{ key: "goods", title: "Товар", sortable: true },
|
||||
{ key: "defects", title: "Брак", sortable: true },
|
||||
{ key: "sellerSupplies", title: "Расходники селлеров", sortable: true },
|
||||
{ key: "pvzReturns", title: "Возвраты с ПВЗ", sortable: true },
|
||||
];
|
||||
|
||||
const sampleData: TableData[] = [
|
||||
{
|
||||
id: 1,
|
||||
number: "001",
|
||||
store: "Wildberries",
|
||||
product: "Смартфон iPhone 15",
|
||||
goods: 150,
|
||||
defects: 5,
|
||||
sellerSupplies: 25,
|
||||
pvzReturns: 12,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
number: "002",
|
||||
store: "Ozon",
|
||||
product: "Наушники AirPods",
|
||||
goods: 89,
|
||||
defects: 3,
|
||||
sellerSupplies: 18,
|
||||
pvzReturns: 8,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
number: "003",
|
||||
store: "Яндекс Маркет",
|
||||
product: "Планшет iPad",
|
||||
goods: 67,
|
||||
defects: 2,
|
||||
sellerSupplies: 12,
|
||||
pvzReturns: 5,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
number: "004",
|
||||
store: "Wildberries",
|
||||
product: "Умные часы",
|
||||
goods: 234,
|
||||
defects: 8,
|
||||
sellerSupplies: 45,
|
||||
pvzReturns: 18,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
number: "005",
|
||||
store: "Ozon",
|
||||
product: "Ноутбук MacBook",
|
||||
goods: 45,
|
||||
defects: 1,
|
||||
sellerSupplies: 8,
|
||||
pvzReturns: 3,
|
||||
},
|
||||
];
|
||||
|
||||
type SortField = keyof TableData | null;
|
||||
type SortDirection = "asc" | "desc";
|
||||
|
||||
export function FulfillmentWarehouseDemo() {
|
||||
const [sortField, setSortField] = useState<SortField>(null);
|
||||
const [sortDirection, setSortDirection] = useState<SortDirection>("asc");
|
||||
|
||||
const handleSort = (field: keyof TableData) => {
|
||||
if (sortField === field) {
|
||||
setSortDirection(sortDirection === "asc" ? "desc" : "asc");
|
||||
} else {
|
||||
setSortField(field);
|
||||
setSortDirection("asc");
|
||||
}
|
||||
};
|
||||
|
||||
const sortedData = [...sampleData].sort((a, b) => {
|
||||
if (!sortField) return 0;
|
||||
|
||||
const aValue = a[sortField];
|
||||
const bValue = b[sortField];
|
||||
|
||||
if (typeof aValue === "string" && typeof bValue === "string") {
|
||||
return sortDirection === "asc"
|
||||
? aValue.localeCompare(bValue)
|
||||
: bValue.localeCompare(aValue);
|
||||
}
|
||||
|
||||
if (typeof aValue === "number" && typeof bValue === "number") {
|
||||
return sortDirection === "asc" ? aValue - bValue : bValue - aValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Вычисляем суммарные значения
|
||||
const totals = {
|
||||
goods: sampleData.reduce((sum, item) => sum + item.goods, 0),
|
||||
defects: sampleData.reduce((sum, item) => sum + item.defects, 0),
|
||||
sellerSupplies: sampleData.reduce(
|
||||
(sum, item) => sum + item.sellerSupplies,
|
||||
0
|
||||
),
|
||||
pvzReturns: sampleData.reduce((sum, item) => sum + item.pvzReturns, 0),
|
||||
};
|
||||
|
||||
const SortIcon = ({ field }: { field: keyof TableData }) => {
|
||||
if (sortField !== field) {
|
||||
return (
|
||||
<div className="flex flex-col ml-1 opacity-30">
|
||||
<ChevronUp size={12} />
|
||||
<ChevronDown size={12} className="-mt-1" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return sortDirection === "asc" ? (
|
||||
<ChevronUp size={14} className="ml-1 text-blue-400" />
|
||||
) : (
|
||||
<ChevronDown size={14} className="ml-1 text-blue-400" />
|
||||
);
|
||||
};
|
||||
|
||||
const StatCell = ({
|
||||
value,
|
||||
prevValue,
|
||||
nextValue,
|
||||
}: {
|
||||
value: number;
|
||||
prevValue: number;
|
||||
nextValue: number;
|
||||
}) => (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<div className="flex items-center text-xs text-green-400">
|
||||
<TrendingUp size={12} className="mr-1" />
|
||||
{prevValue}
|
||||
</div>
|
||||
<div className="text-lg font-semibold text-white">
|
||||
{value.toLocaleString()}
|
||||
</div>
|
||||
<div className="flex items-center text-xs text-red-400">
|
||||
<TrendingDown size={12} className="mr-1" />
|
||||
{nextValue}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white mb-4">Склад фулфилмент</h2>
|
||||
<p className="text-white/70 mb-6">
|
||||
Многоуровневая таблица с сортировкой и агрегированными данными
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 backdrop-blur border border-white/10 rounded-lg overflow-hidden">
|
||||
{/* Заголовки таблицы */}
|
||||
<div className="bg-white/10 border-b border-white/10">
|
||||
<div className="grid grid-cols-7 gap-4 p-4">
|
||||
{columns.map((column) => (
|
||||
<div
|
||||
key={column.key}
|
||||
className={`flex items-center justify-center text-sm font-medium text-white/90 ${
|
||||
column.sortable ? "cursor-pointer hover:text-white" : ""
|
||||
}`}
|
||||
onClick={() =>
|
||||
column.sortable && handleSort(column.key as keyof TableData)
|
||||
}
|
||||
>
|
||||
{column.title}
|
||||
{column.sortable && (
|
||||
<SortIcon field={column.key as keyof TableData} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Суммарные значения */}
|
||||
<div className="bg-white/5 border-b border-white/10">
|
||||
<div className="grid grid-cols-7 gap-4 p-6">
|
||||
<div className="text-center">
|
||||
<div className="text-sm text-white/60">Всего</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-sm text-white/60">Магазинов</div>
|
||||
<div className="text-lg font-semibold text-white">3</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-sm text-white/60">Продуктов</div>
|
||||
<div className="text-lg font-semibold text-white">5</div>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-sm text-white/60 mb-2">Товар</div>
|
||||
<StatCell value={totals.goods} prevValue={520} nextValue={610} />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-sm text-white/60 mb-2">Брак</div>
|
||||
<StatCell value={totals.defects} prevValue={15} nextValue={25} />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-sm text-white/60 mb-2">Расходники</div>
|
||||
<StatCell
|
||||
value={totals.sellerSupplies}
|
||||
prevValue={95}
|
||||
nextValue={125}
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-sm text-white/60 mb-2">Возвраты</div>
|
||||
<StatCell
|
||||
value={totals.pvzReturns}
|
||||
prevValue={38}
|
||||
nextValue={55}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Данные таблицы */}
|
||||
<div className="divide-y divide-white/10">
|
||||
{sortedData.map((row) => (
|
||||
<div
|
||||
key={row.id}
|
||||
className="grid grid-cols-7 gap-4 p-4 hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<div className="text-center text-white/80">{row.number}</div>
|
||||
<div className="text-center text-white/80">{row.store}</div>
|
||||
<div
|
||||
className="text-center text-white/80 truncate"
|
||||
title={row.product}
|
||||
>
|
||||
{row.product}
|
||||
</div>
|
||||
<div className="text-center text-white font-medium">
|
||||
{row.goods.toLocaleString()}
|
||||
</div>
|
||||
<div className="text-center text-red-400 font-medium">
|
||||
{row.defects}
|
||||
</div>
|
||||
<div className="text-center text-yellow-400 font-medium">
|
||||
{row.sellerSupplies}
|
||||
</div>
|
||||
<div className="text-center text-orange-400 font-medium">
|
||||
{row.pvzReturns}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Дополнительная информация */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="bg-white/5 backdrop-blur border border-white/10 rounded-lg p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-2">
|
||||
Особенности таблицы
|
||||
</h3>
|
||||
<ul className="text-sm text-white/70 space-y-1">
|
||||
<li>• Сортировка по всем столбцам</li>
|
||||
<li>• Агрегированные данные с индикаторами</li>
|
||||
<li>• Многоуровневая структура</li>
|
||||
<li>• Цветовое кодирование данных</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 backdrop-blur border border-white/10 rounded-lg p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-2">Индикаторы</h3>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex items-center">
|
||||
<TrendingUp size={16} className="text-green-400 mr-2" />
|
||||
<span className="text-white/70">Рост (левое значение)</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<TrendingDown size={16} className="text-red-400 mr-2" />
|
||||
<span className="text-white/70">Падение (правое значение)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 backdrop-blur border border-white/10 rounded-lg p-4">
|
||||
<h3 className="text-lg font-semibold text-white mb-2">
|
||||
Цветовая схема
|
||||
</h3>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex items-center">
|
||||
<div className="w-3 h-3 bg-white rounded mr-2"></div>
|
||||
<span className="text-white/70">Товары</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-3 h-3 bg-red-400 rounded mr-2"></div>
|
||||
<span className="text-white/70">Брак</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-3 h-3 bg-yellow-400 rounded mr-2"></div>
|
||||
<span className="text-white/70">Расходники</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<div className="w-3 h-3 bg-orange-400 rounded mr-2"></div>
|
||||
<span className="text-white/70">Возвраты</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user