From ead17fd56ceb3b47cdd13328d44a0aae13fc1e69 Mon Sep 17 00:00:00 2001 From: Veronika Smirnova Date: Mon, 21 Jul 2025 13:25:16 +0300 Subject: [PATCH] feat: implement comprehensive supplies management system - Create new supplies dashboard with multi-level navigation structure - Add fulfillment supplies section with goods/materials/returns tabs - Add marketplace supplies section with Wildberries/Ozon tabs - Update existing supplies pages to use new dashboard - Fix height alignment in statistics blocks - Remove redundant title from materials supplies section --- src/app/fulfillment-supplies/page.tsx | 10 +- src/app/supplies/page.tsx | 2 +- .../fulfillment-goods-tab.tsx | 280 +++++++++++++++ .../fulfillment-supplies-tab.tsx | 60 ++++ .../fulfillment-supplies/pvz-returns-tab.tsx | 336 ++++++++++++++++++ .../seller-materials-tab.tsx | 296 +++++++++++++++ .../marketplace-supplies-tab.tsx | 52 +++ .../ozon-supplies-tab.tsx | 326 +++++++++++++++++ .../wildberries-supplies-tab.tsx | 322 +++++++++++++++++ .../materials-supplies-tab.tsx | 64 ++-- .../supplies-dashboard.tsx | 122 +++++++ 11 files changed, 1830 insertions(+), 40 deletions(-) create mode 100644 src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-tab.tsx create mode 100644 src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx create mode 100644 src/components/fulfillment-supplies/fulfillment-supplies/pvz-returns-tab.tsx create mode 100644 src/components/fulfillment-supplies/fulfillment-supplies/seller-materials-tab.tsx create mode 100644 src/components/fulfillment-supplies/marketplace-supplies/marketplace-supplies-tab.tsx create mode 100644 src/components/fulfillment-supplies/marketplace-supplies/ozon-supplies-tab.tsx create mode 100644 src/components/fulfillment-supplies/marketplace-supplies/wildberries-supplies-tab.tsx create mode 100644 src/components/fulfillment-supplies/supplies-dashboard.tsx diff --git a/src/app/fulfillment-supplies/page.tsx b/src/app/fulfillment-supplies/page.tsx index f86db01..d0ec8aa 100644 --- a/src/app/fulfillment-supplies/page.tsx +++ b/src/app/fulfillment-supplies/page.tsx @@ -1,10 +1,10 @@ -import { AuthGuard } from "@/components/auth-guard" -import { FulfillmentSuppliesDashboard } from "@/components/fulfillment-supplies/fulfillment-supplies-dashboard" +import { AuthGuard } from "@/components/auth-guard"; +import { SuppliesDashboard } from "@/components/fulfillment-supplies/supplies-dashboard"; export default function FulfillmentSuppliesPage() { return ( - + - ) -} \ No newline at end of file + ); +} diff --git a/src/app/supplies/page.tsx b/src/app/supplies/page.tsx index c5b3e2d..5f5720e 100644 --- a/src/app/supplies/page.tsx +++ b/src/app/supplies/page.tsx @@ -1,5 +1,5 @@ import { AuthGuard } from "@/components/auth-guard" -import { SuppliesDashboard } from "@/components/supplies/supplies-dashboard" +import { SuppliesDashboard } from "@/components/fulfillment-supplies/supplies-dashboard" export default function SuppliesPage() { return ( diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-tab.tsx new file mode 100644 index 0000000..21bb77c --- /dev/null +++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-tab.tsx @@ -0,0 +1,280 @@ +"use client"; + +import { useState } from "react"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Package, + Plus, + Search, + Filter, + TrendingUp, + AlertCircle, + Calendar, + Eye, +} from "lucide-react"; + +// Мок данные для товаров +const mockGoodsSupplies = [ + { + id: "1", + productName: "Смартфон iPhone 15", + sku: "IPH15-128-BLK", + seller: "TechStore LLC", + quantity: 50, + expectedDate: "2024-01-15", + status: "planned", + totalValue: 2500000, + warehouse: "Склад А1", + }, + { + id: "2", + productName: "Ноутбук MacBook Air", + sku: "MBA-M2-256-SLV", + seller: "Apple Reseller", + quantity: 25, + expectedDate: "2024-01-12", + status: "in-transit", + totalValue: 3750000, + warehouse: "Склад Б2", + }, + { + id: "3", + productName: "Наушники AirPods Pro", + sku: "APP-2GEN-WHT", + seller: "Audio World", + quantity: 100, + expectedDate: "2024-01-10", + status: "delivered", + totalValue: 2800000, + warehouse: "Склад А1", + }, +]; + +export function FulfillmentGoodsTab() { + const [searchTerm, setSearchTerm] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("ru-RU", { + style: "currency", + currency: "RUB", + minimumFractionDigits: 0, + }).format(amount); + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }); + }; + + const getStatusBadge = (status: string) => { + const statusConfig = { + planned: { + color: "text-blue-300 border-blue-400/30", + label: "Запланировано", + }, + "in-transit": { + color: "text-yellow-300 border-yellow-400/30", + label: "В пути", + }, + delivered: { + color: "text-green-300 border-green-400/30", + label: "Доставлено", + }, + "in-processing": { + color: "text-purple-300 border-purple-400/30", + label: "Обрабатывается", + }, + }; + + const config = + statusConfig[status as keyof typeof statusConfig] || statusConfig.planned; + + return ( + + {config.label} + + ); + }; + + const filteredSupplies = mockGoodsSupplies.filter((supply) => { + const matchesSearch = + supply.productName.toLowerCase().includes(searchTerm.toLowerCase()) || + supply.sku.toLowerCase().includes(searchTerm.toLowerCase()) || + supply.seller.toLowerCase().includes(searchTerm.toLowerCase()); + + const matchesStatus = + statusFilter === "all" || supply.status === statusFilter; + + return matchesSearch && matchesStatus; + }); + + const getTotalValue = () => { + return filteredSupplies.reduce((sum, supply) => sum + supply.totalValue, 0); + }; + + const getTotalQuantity = () => { + return filteredSupplies.reduce((sum, supply) => sum + supply.quantity, 0); + }; + + return ( +
+ {/* Статистика с кнопкой */} +
+
+ +
+
+ +
+
+

Поставок

+

+ {filteredSupplies.length} +

+
+
+
+ + +
+
+ +
+
+

Стоимость

+

+ {formatCurrency(getTotalValue())} +

+
+
+
+ + +
+
+ +
+
+

Товаров

+

+ {getTotalQuantity()} +

+
+
+
+
+ + +
+ + {/* Фильтры */} +
+
+ + setSearchTerm(e.target.value)} + className="glass-input pl-10 text-white placeholder:text-white/40" + /> +
+ + +
+ + {/* Список поставок */} +
+
+ {filteredSupplies.map((supply) => ( + +
+
+
+

+ {supply.productName} +

+ {getStatusBadge(supply.status)} +
+ +
+
+

SKU

+

{supply.sku}

+
+
+

Селлер

+

{supply.seller}

+
+
+

Количество

+

+ {supply.quantity} шт. +

+
+
+

Ожидается

+

+ {formatDate(supply.expectedDate)} +

+
+
+ +
+
+ + Склад:{" "} + {supply.warehouse} + + + Стоимость:{" "} + + {formatCurrency(supply.totalValue)} + + +
+
+
+ +
+ +
+
+
+ ))} +
+
+
+ ); +} diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx new file mode 100644 index 0000000..cd293a6 --- /dev/null +++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx @@ -0,0 +1,60 @@ +"use client"; + +import { useState } from "react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Package, Wrench, RotateCcw } from "lucide-react"; + +// Импорты компонентов подкатегорий +import { FulfillmentGoodsTab } from "./fulfillment-goods-tab"; +import { SellerMaterialsTab } from "./seller-materials-tab"; +import { PvzReturnsTab } from "./pvz-returns-tab"; + +export function FulfillmentSuppliesTab() { + const [activeTab, setActiveTab] = useState("goods"); + + return ( +
+ + + + + Товары + + + + Расходники селлеров + + + + Возвраты с ПВЗ + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/pvz-returns-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/pvz-returns-tab.tsx new file mode 100644 index 0000000..5a47fd9 --- /dev/null +++ b/src/components/fulfillment-supplies/fulfillment-supplies/pvz-returns-tab.tsx @@ -0,0 +1,336 @@ +"use client"; + +import { useState } from "react"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + RotateCcw, + Plus, + Search, + TrendingUp, + AlertCircle, + Eye, + MapPin, +} from "lucide-react"; + +// Мок данные для возвратов с ПВЗ +const mockPvzReturns = [ + { + id: "1", + productName: "Смартфон Samsung Galaxy S23", + sku: "SAM-S23-128-BLK", + pvzAddress: "ул. Ленина, 15, ПВЗ №1234", + returnDate: "2024-01-13", + status: "collected", + quantity: 3, + reason: "Брак товара", + estimatedValue: 150000, + seller: "TechWorld", + }, + { + id: "2", + productName: "Кроссовки Nike Air Max", + sku: "NIKE-AM-42-WHT", + pvzAddress: "пр. Мира, 88, ПВЗ №5678", + returnDate: "2024-01-12", + status: "pending", + quantity: 2, + reason: "Не подошел размер", + estimatedValue: 24000, + seller: "SportsGear", + }, + { + id: "3", + productName: "Планшет iPad Air", + sku: "IPAD-AIR-256-GRY", + pvzAddress: "ул. Советская, 42, ПВЗ №9012", + returnDate: "2024-01-11", + status: "processed", + quantity: 1, + reason: "Передумал покупать", + estimatedValue: 85000, + seller: "AppleStore", + }, +]; + +export function PvzReturnsTab() { + const [searchTerm, setSearchTerm] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("ru-RU", { + style: "currency", + currency: "RUB", + minimumFractionDigits: 0, + }).format(amount); + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }); + }; + + const getStatusBadge = (status: string) => { + const statusConfig = { + pending: { + color: "text-yellow-300 border-yellow-400/30", + label: "Ожидает сбора", + }, + collected: { + color: "text-blue-300 border-blue-400/30", + label: "Собрано", + }, + processed: { + color: "text-green-300 border-green-400/30", + label: "Обработано", + }, + disposed: { + color: "text-red-300 border-red-400/30", + label: "Утилизировано", + }, + }; + + const config = + statusConfig[status as keyof typeof statusConfig] || statusConfig.pending; + + return ( + + {config.label} + + ); + }; + + const getReasonBadge = (reason: string) => { + const reasonConfig = { + "Брак товара": { color: "text-red-300 border-red-400/30" }, + "Не подошел размер": { color: "text-orange-300 border-orange-400/30" }, + "Передумал покупать": { color: "text-blue-300 border-blue-400/30" }, + Другое: { color: "text-gray-300 border-gray-400/30" }, + }; + + const config = + reasonConfig[reason as keyof typeof reasonConfig] || + reasonConfig["Другое"]; + + return ( + + {reason} + + ); + }; + + const filteredReturns = mockPvzReturns.filter((returnItem) => { + const matchesSearch = + returnItem.productName.toLowerCase().includes(searchTerm.toLowerCase()) || + returnItem.sku.toLowerCase().includes(searchTerm.toLowerCase()) || + returnItem.pvzAddress.toLowerCase().includes(searchTerm.toLowerCase()) || + returnItem.seller.toLowerCase().includes(searchTerm.toLowerCase()); + + const matchesStatus = + statusFilter === "all" || returnItem.status === statusFilter; + + return matchesSearch && matchesStatus; + }); + + const getTotalValue = () => { + return filteredReturns.reduce( + (sum, returnItem) => sum + returnItem.estimatedValue, + 0 + ); + }; + + const getTotalQuantity = () => { + return filteredReturns.reduce( + (sum, returnItem) => sum + returnItem.quantity, + 0 + ); + }; + + const getPendingCount = () => { + return filteredReturns.filter( + (returnItem) => returnItem.status === "pending" + ).length; + }; + + return ( +
+ {/* Статистика с кнопкой */} +
+
+ +
+
+ +
+
+

Возвратов

+

+ {filteredReturns.length} +

+
+
+
+ + +
+
+ +
+
+

Ожидает сбора

+

+ {getPendingCount()} +

+
+
+
+ + +
+
+ +
+
+

Стоимость

+

+ {formatCurrency(getTotalValue())} +

+
+
+
+ + +
+
+ +
+
+

Товаров

+

+ {getTotalQuantity()} +

+
+
+
+
+ + +
+ + {/* Фильтры */} +
+
+ + setSearchTerm(e.target.value)} + className="glass-input pl-10 text-white placeholder:text-white/40" + /> +
+ + +
+ + {/* Список возвратов */} +
+
+ {filteredReturns.map((returnItem) => ( + +
+
+
+

+ {returnItem.productName} +

+ {getStatusBadge(returnItem.status)} + {getReasonBadge(returnItem.reason)} +
+ +
+
+

SKU

+

{returnItem.sku}

+
+
+

Селлер

+

{returnItem.seller}

+
+
+

Количество

+

+ {returnItem.quantity} шт. +

+
+
+

Дата возврата

+

+ {formatDate(returnItem.returnDate)} +

+
+
+ +
+

+ + ПВЗ:{" "} + + {returnItem.pvzAddress} + +

+
+ +
+ + Оценочная стоимость:{" "} + + {formatCurrency(returnItem.estimatedValue)} + + +
+
+ +
+ +
+
+
+ ))} +
+
+
+ ); +} diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/seller-materials-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/seller-materials-tab.tsx new file mode 100644 index 0000000..e744695 --- /dev/null +++ b/src/components/fulfillment-supplies/fulfillment-supplies/seller-materials-tab.tsx @@ -0,0 +1,296 @@ +"use client"; + +import { useState } from "react"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Wrench, + Plus, + Search, + TrendingUp, + AlertCircle, + Eye, +} from "lucide-react"; + +// Мок данные для расходников селлеров +const mockSellerMaterials = [ + { + id: "1", + materialName: "Пакеты полиэтиленовые 30х40", + seller: "PackStore LLC", + category: "Упаковка", + quantity: 10000, + expectedDate: "2024-01-14", + status: "planned", + unitPrice: 2.5, + totalValue: 25000, + purpose: "Упаковка мелких товаров", + }, + { + id: "2", + materialName: "Скотч упаковочный прозрачный", + seller: "Packaging Pro", + category: "Упаковка", + quantity: 500, + expectedDate: "2024-01-11", + status: "in-transit", + unitPrice: 85, + totalValue: 42500, + purpose: "Заклейка коробок", + }, + { + id: "3", + materialName: "Этикетки штрих-код 58х40", + seller: "LabelTech", + category: "Маркировка", + quantity: 50000, + expectedDate: "2024-01-09", + status: "delivered", + unitPrice: 0.8, + totalValue: 40000, + purpose: "Маркировка товаров", + }, +]; + +export function SellerMaterialsTab() { + const [searchTerm, setSearchTerm] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("ru-RU", { + style: "currency", + currency: "RUB", + minimumFractionDigits: 0, + }).format(amount); + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }); + }; + + const getStatusBadge = (status: string) => { + const statusConfig = { + planned: { + color: "text-blue-300 border-blue-400/30", + label: "Запланировано", + }, + "in-transit": { + color: "text-yellow-300 border-yellow-400/30", + label: "В пути", + }, + delivered: { + color: "text-green-300 border-green-400/30", + label: "Доставлено", + }, + "in-processing": { + color: "text-purple-300 border-purple-400/30", + label: "Обрабатывается", + }, + }; + + const config = + statusConfig[status as keyof typeof statusConfig] || statusConfig.planned; + + return ( + + {config.label} + + ); + }; + + const filteredMaterials = mockSellerMaterials.filter((material) => { + const matchesSearch = + material.materialName.toLowerCase().includes(searchTerm.toLowerCase()) || + material.seller.toLowerCase().includes(searchTerm.toLowerCase()) || + material.category.toLowerCase().includes(searchTerm.toLowerCase()); + + const matchesStatus = + statusFilter === "all" || material.status === statusFilter; + + return matchesSearch && matchesStatus; + }); + + const getTotalValue = () => { + return filteredMaterials.reduce( + (sum, material) => sum + material.totalValue, + 0 + ); + }; + + const getTotalQuantity = () => { + return filteredMaterials.reduce( + (sum, material) => sum + material.quantity, + 0 + ); + }; + + return ( +
+ {/* Статистика с кнопкой */} +
+
+ +
+
+ +
+
+

Поставок

+

+ {filteredMaterials.length} +

+
+
+
+ + +
+
+ +
+
+

Стоимость

+

+ {formatCurrency(getTotalValue())} +

+
+
+
+ + +
+
+ +
+
+

Единиц

+

+ {getTotalQuantity().toLocaleString()} +

+
+
+
+
+ + +
+ + {/* Фильтры */} +
+
+ + setSearchTerm(e.target.value)} + className="glass-input pl-10 text-white placeholder:text-white/40" + /> +
+ + +
+ + {/* Список материалов */} +
+
+ {filteredMaterials.map((material) => ( + +
+
+
+

+ {material.materialName} +

+ {getStatusBadge(material.status)} +
+ +
+
+

Селлер

+

{material.seller}

+
+
+

Категория

+

{material.category}

+
+
+

Количество

+

+ {material.quantity.toLocaleString()} шт. +

+
+
+

Ожидается

+

+ {formatDate(material.expectedDate)} +

+
+
+ +
+
+ + Цена за ед.:{" "} + + {formatCurrency(material.unitPrice)} + + + + Общая стоимость:{" "} + + {formatCurrency(material.totalValue)} + + +
+
+ +
+

+ Назначение:{" "} + {material.purpose} +

+
+
+ +
+ +
+
+
+ ))} +
+
+
+ ); +} diff --git a/src/components/fulfillment-supplies/marketplace-supplies/marketplace-supplies-tab.tsx b/src/components/fulfillment-supplies/marketplace-supplies/marketplace-supplies-tab.tsx new file mode 100644 index 0000000..c6eaea7 --- /dev/null +++ b/src/components/fulfillment-supplies/marketplace-supplies/marketplace-supplies-tab.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { useState } from "react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { ShoppingCart, Package } from "lucide-react"; + +// Импорты компонентов маркетплейсов +import { WildberriesSuppliesTab } from "./wildberries-supplies-tab"; +import { OzonSuppliesTab } from "./ozon-supplies-tab"; + +export function MarketplaceSuppliesTab() { + const [activeTab, setActiveTab] = useState("wildberries"); + + return ( +
+ + + +
+ W +
+ Wildberries +
+ +
+ O +
+ Ozon +
+
+ + + + + + + + +
+
+ ); +} diff --git a/src/components/fulfillment-supplies/marketplace-supplies/ozon-supplies-tab.tsx b/src/components/fulfillment-supplies/marketplace-supplies/ozon-supplies-tab.tsx new file mode 100644 index 0000000..59c4d3e --- /dev/null +++ b/src/components/fulfillment-supplies/marketplace-supplies/ozon-supplies-tab.tsx @@ -0,0 +1,326 @@ +"use client"; + +import { useState } from "react"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Package, + Plus, + Search, + TrendingUp, + AlertCircle, + Eye, + Calendar, +} from "lucide-react"; + +// Мок данные для поставок на Ozon +const mockOzonSupplies = [ + { + id: "1", + supplyId: "OZ-SP-240113-001", + warehouse: "Тверь", + deliveryDate: "2024-01-16", + status: "awaiting_packaging", + totalItems: 120, + totalBoxes: 10, + estimatedValue: 380000, + products: [ + { name: "Телефон Samsung A54", quantity: 40, price: 6500 }, + { name: "Чехол силиконовый", quantity: 80, price: 850 }, + ], + }, + { + id: "2", + supplyId: "OZ-SP-240112-002", + warehouse: "Казань", + deliveryDate: "2024-01-15", + status: "sent_to_delivery", + totalItems: 75, + totalBoxes: 6, + estimatedValue: 295000, + products: [ + { name: "Наушники беспроводные", quantity: 25, price: 4200 }, + { name: "Зарядное устройство", quantity: 50, price: 1800 }, + ], + }, + { + id: "3", + supplyId: "OZ-SP-240111-003", + warehouse: "Екатеринбург", + deliveryDate: "2024-01-14", + status: "delivered", + totalItems: 180, + totalBoxes: 14, + estimatedValue: 520000, + products: [ + { name: "Планшет Xiaomi Pad", quantity: 60, price: 4800 }, + { name: "Клавиатура беспроводная", quantity: 120, price: 1650 }, + ], + }, +]; + +export function OzonSuppliesTab() { + const [searchTerm, setSearchTerm] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("ru-RU", { + style: "currency", + currency: "RUB", + minimumFractionDigits: 0, + }).format(amount); + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }); + }; + + const getStatusBadge = (status: string) => { + const statusConfig = { + awaiting_packaging: { + color: "text-blue-300 border-blue-400/30", + label: "Ожидает упаковки", + }, + sent_to_delivery: { + color: "text-yellow-300 border-yellow-400/30", + label: "Отправлена", + }, + delivered: { + color: "text-green-300 border-green-400/30", + label: "Доставлена", + }, + cancelled: { color: "text-red-300 border-red-400/30", label: "Отменена" }, + arbitration: { + color: "text-orange-300 border-orange-400/30", + label: "Арбитраж", + }, + }; + + const config = + statusConfig[status as keyof typeof statusConfig] || + statusConfig.awaiting_packaging; + + return ( + + {config.label} + + ); + }; + + const filteredSupplies = mockOzonSupplies.filter((supply) => { + const matchesSearch = + supply.supplyId.toLowerCase().includes(searchTerm.toLowerCase()) || + supply.warehouse.toLowerCase().includes(searchTerm.toLowerCase()) || + supply.products.some((p) => + p.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const matchesStatus = + statusFilter === "all" || supply.status === statusFilter; + + return matchesSearch && matchesStatus; + }); + + const getTotalValue = () => { + return filteredSupplies.reduce( + (sum, supply) => sum + supply.estimatedValue, + 0 + ); + }; + + const getTotalItems = () => { + return filteredSupplies.reduce((sum, supply) => sum + supply.totalItems, 0); + }; + + const getTotalBoxes = () => { + return filteredSupplies.reduce((sum, supply) => sum + supply.totalBoxes, 0); + }; + + return ( +
+ {/* Статистика с кнопкой */} +
+
+ +
+
+ +
+
+

Поставок

+

+ {filteredSupplies.length} +

+
+
+
+ + +
+
+ +
+
+

Стоимость

+

+ {formatCurrency(getTotalValue())} +

+
+
+
+ + +
+
+ +
+
+

Товаров

+

+ {getTotalItems()} +

+
+
+
+ + +
+
+ +
+
+

Коробок

+

+ {getTotalBoxes()} +

+
+
+
+
+ + +
+ + {/* Фильтры */} +
+
+ + setSearchTerm(e.target.value)} + className="glass-input pl-10 text-white placeholder:text-white/40" + /> +
+ + +
+ + {/* Список поставок */} +
+
+ {filteredSupplies.map((supply) => ( + +
+
+
+
+ O +
+

+ {supply.supplyId} +

+ {getStatusBadge(supply.status)} +
+ +
+
+

Склад Ozon

+

{supply.warehouse}

+
+
+

Дата доставки

+

+ + {formatDate(supply.deliveryDate)} +

+
+
+

Товаров / Коробок

+

+ {supply.totalItems} / {supply.totalBoxes} +

+
+
+

Стоимость

+

+ {formatCurrency(supply.estimatedValue)} +

+
+
+ + {/* Список товаров в поставке */} +
+

+ Товары в поставке: +

+
+ {supply.products.map((product, index) => ( +
+ {product.name} + + {product.quantity} шт. ×{" "} + {formatCurrency(product.price)} + +
+ ))} +
+
+
+ +
+ +
+
+
+ ))} +
+
+
+ ); +} diff --git a/src/components/fulfillment-supplies/marketplace-supplies/wildberries-supplies-tab.tsx b/src/components/fulfillment-supplies/marketplace-supplies/wildberries-supplies-tab.tsx new file mode 100644 index 0000000..819933d --- /dev/null +++ b/src/components/fulfillment-supplies/marketplace-supplies/wildberries-supplies-tab.tsx @@ -0,0 +1,322 @@ +"use client"; + +import { useState } from "react"; +import { Card } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Badge } from "@/components/ui/badge"; +import { + Package, + Plus, + Search, + TrendingUp, + AlertCircle, + Eye, + Calendar, +} from "lucide-react"; + +// Мок данные для поставок на Wildberries +const mockWbSupplies = [ + { + id: "1", + supplyId: "WB-SP-240113-001", + warehouse: "Коледино", + deliveryDate: "2024-01-15", + status: "created", + totalItems: 150, + totalBoxes: 12, + estimatedValue: 450000, + products: [ + { name: "Футболка базовая", quantity: 100, price: 1200 }, + { name: "Джинсы классические", quantity: 50, price: 3500 }, + ], + }, + { + id: "2", + supplyId: "WB-SP-240112-002", + warehouse: "Электросталь", + deliveryDate: "2024-01-14", + status: "confirmed", + totalItems: 85, + totalBoxes: 8, + estimatedValue: 320000, + products: [ + { name: "Кроссовки спортивные", quantity: 35, price: 4500 }, + { name: "Рюкзак молодежный", quantity: 50, price: 2800 }, + ], + }, + { + id: "3", + supplyId: "WB-SP-240111-003", + warehouse: "Подольск", + deliveryDate: "2024-01-13", + status: "shipped", + totalItems: 200, + totalBoxes: 15, + estimatedValue: 680000, + products: [ + { name: "Платье летнее", quantity: 80, price: 2200 }, + { name: "Блузка офисная", quantity: 120, price: 3800 }, + ], + }, +]; + +export function WildberriesSuppliesTab() { + const [searchTerm, setSearchTerm] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat("ru-RU", { + style: "currency", + currency: "RUB", + minimumFractionDigits: 0, + }).format(amount); + }; + + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString("ru-RU", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }); + }; + + const getStatusBadge = (status: string) => { + const statusConfig = { + created: { color: "text-blue-300 border-blue-400/30", label: "Создана" }, + confirmed: { + color: "text-yellow-300 border-yellow-400/30", + label: "Подтверждена", + }, + shipped: { + color: "text-green-300 border-green-400/30", + label: "Отправлена", + }, + delivered: { + color: "text-purple-300 border-purple-400/30", + label: "Доставлена", + }, + cancelled: { color: "text-red-300 border-red-400/30", label: "Отменена" }, + }; + + const config = + statusConfig[status as keyof typeof statusConfig] || statusConfig.created; + + return ( + + {config.label} + + ); + }; + + const filteredSupplies = mockWbSupplies.filter((supply) => { + const matchesSearch = + supply.supplyId.toLowerCase().includes(searchTerm.toLowerCase()) || + supply.warehouse.toLowerCase().includes(searchTerm.toLowerCase()) || + supply.products.some((p) => + p.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const matchesStatus = + statusFilter === "all" || supply.status === statusFilter; + + return matchesSearch && matchesStatus; + }); + + const getTotalValue = () => { + return filteredSupplies.reduce( + (sum, supply) => sum + supply.estimatedValue, + 0 + ); + }; + + const getTotalItems = () => { + return filteredSupplies.reduce((sum, supply) => sum + supply.totalItems, 0); + }; + + const getTotalBoxes = () => { + return filteredSupplies.reduce((sum, supply) => sum + supply.totalBoxes, 0); + }; + + return ( +
+ {/* Статистика с кнопкой */} +
+
+ +
+
+ +
+
+

Поставок

+

+ {filteredSupplies.length} +

+
+
+
+ + +
+
+ +
+
+

Стоимость

+

+ {formatCurrency(getTotalValue())} +

+
+
+
+ + +
+
+ +
+
+

Товаров

+

+ {getTotalItems()} +

+
+
+
+ + +
+
+ +
+
+

Коробок

+

+ {getTotalBoxes()} +

+
+
+
+
+ + +
+ + {/* Фильтры */} +
+
+ + setSearchTerm(e.target.value)} + className="glass-input pl-10 text-white placeholder:text-white/40" + /> +
+ + +
+ + {/* Список поставок */} +
+
+ {filteredSupplies.map((supply) => ( + +
+
+
+
+ W +
+

+ {supply.supplyId} +

+ {getStatusBadge(supply.status)} +
+ +
+
+

Склад WB

+

{supply.warehouse}

+
+
+

Дата доставки

+

+ + {formatDate(supply.deliveryDate)} +

+
+
+

Товаров / Коробок

+

+ {supply.totalItems} / {supply.totalBoxes} +

+
+
+

Стоимость

+

+ {formatCurrency(supply.estimatedValue)} +

+
+
+ + {/* Список товаров в поставке */} +
+

+ Товары в поставке: +

+
+ {supply.products.map((product, index) => ( +
+ {product.name} + + {product.quantity} шт. ×{" "} + {formatCurrency(product.price)} + +
+ ))} +
+
+
+ +
+ +
+
+
+ ))} +
+
+
+ ); +} diff --git a/src/components/fulfillment-supplies/materials-supplies/materials-supplies-tab.tsx b/src/components/fulfillment-supplies/materials-supplies/materials-supplies-tab.tsx index 75c2f84..025e571 100644 --- a/src/components/fulfillment-supplies/materials-supplies/materials-supplies-tab.tsx +++ b/src/components/fulfillment-supplies/materials-supplies/materials-supplies-tab.tsx @@ -167,26 +167,11 @@ export function MaterialsSuppliesTab() { return (
- {/* Компактный заголовок */} -
-
- - Расходники -
- -
- - {/* Компактная статистика */} -
- -
+ {/* Статистика с кнопкой заказа */} +
+
+ +
@@ -197,8 +182,8 @@ export function MaterialsSuppliesTab() {
- -
+ +
@@ -209,8 +194,8 @@ export function MaterialsSuppliesTab() {
- -
+ +
@@ -221,17 +206,28 @@ export function MaterialsSuppliesTab() {
- -
-
- + +
+
+ +
+
+

Низкий остаток

+

{getLowStockCount()}

+
-
-

Низкий остаток

-

{getLowStockCount()}

-
-
- + +
+ + {/* Кнопка заказа */} +
{/* Компактный поиск и фильтры */} diff --git a/src/components/fulfillment-supplies/supplies-dashboard.tsx b/src/components/fulfillment-supplies/supplies-dashboard.tsx new file mode 100644 index 0000000..e4c553b --- /dev/null +++ b/src/components/fulfillment-supplies/supplies-dashboard.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { useState } from "react"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Card } from "@/components/ui/card"; +import { Sidebar } from "@/components/dashboard/sidebar"; +import { useSidebar } from "@/hooks/useSidebar"; +import { + Package, + Wrench, + Truck, + ArrowLeftRight, + Building, + ShoppingCart, +} from "lucide-react"; + +// Импорты компонентов +import { MaterialsSuppliesTab } from "./materials-supplies/materials-supplies-tab"; +import { FulfillmentSuppliesTab } from "./fulfillment-supplies/fulfillment-supplies-tab"; +import { MarketplaceSuppliesTab } from "./marketplace-supplies/marketplace-supplies-tab"; + +export function SuppliesDashboard() { + const { getSidebarMargin } = useSidebar(); + const [mainTab, setMainTab] = useState("goods"); + const [goodsSubTab, setGoodsSubTab] = useState("fulfillment"); + + return ( +
+ +
+
+ {/* Заголовок */} +
+

Поставки

+

+ Управление поставками товаров и расходников +

+
+ + {/* Основная навигация */} +
+ + + + + Поставки товаров + + + + Поставки расходников + + + + {/* Поставки товаров */} + + + + + + Наш фулфилмент + + + + Маркетплейсы + + + + + + + + + + + + + + + + + + {/* Поставки расходников */} + + + + + + +
+
+
+
+ ); +}