diff --git a/src/app/fulfillment-supplies/page.tsx b/src/app/fulfillment-supplies/page.tsx
index f86db01..30c66ce 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 { FulfillmentSuppliesDashboard } from "@/components/fulfillment-supplies/fulfillment-supplies-dashboard";
export default function FulfillmentSuppliesPage() {
return (
- )
-}
\ No newline at end of file
+ );
+}
diff --git a/src/app/logistics/page.tsx b/src/app/logistics/page.tsx
new file mode 100644
index 0000000..023949e
--- /dev/null
+++ b/src/app/logistics/page.tsx
@@ -0,0 +1,10 @@
+import { AuthGuard } from "@/components/auth-guard";
+import { LogisticsDashboard } from "@/components/logistics/logistics-dashboard";
+
+export default function LogisticsPage() {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/dashboard/sidebar.tsx b/src/components/dashboard/sidebar.tsx
index 9b46382..6992dd0 100644
--- a/src/components/dashboard/sidebar.tsx
+++ b/src/components/dashboard/sidebar.tsx
@@ -83,11 +83,22 @@ export function Sidebar() {
}
const handleSuppliesClick = () => {
- // Для фулфилмент кабинетов используем новый роут
- if (user?.organization?.type === 'FULFILLMENT') {
- router.push('/fulfillment-supplies')
- } else {
- router.push('/supplies')
+ // Для каждого типа кабинета свой роут
+ switch (user?.organization?.type) {
+ case 'FULFILLMENT':
+ router.push('/fulfillment-supplies')
+ break
+ case 'SELLER':
+ router.push('/supplies')
+ break
+ case 'WHOLESALE':
+ router.push('/supplies')
+ break
+ case 'LOGIST':
+ router.push('/logistics')
+ break
+ default:
+ router.push('/supplies')
}
}
@@ -103,7 +114,7 @@ export function Sidebar() {
const isServicesActive = pathname.startsWith('/services')
const isWarehouseActive = pathname.startsWith('/warehouse')
const isEmployeesActive = pathname.startsWith('/employees')
- const isSuppliesActive = pathname.startsWith('/supplies') || pathname.startsWith('/fulfillment-supplies')
+ const isSuppliesActive = pathname.startsWith('/supplies') || pathname.startsWith('/fulfillment-supplies') || pathname.startsWith('/logistics')
const isPartnersActive = pathname.startsWith('/partners')
return (
@@ -270,8 +281,8 @@ export function Sidebar() {
)}
- {/* Поставки - для селлеров и фулфилмент */}
- {(user?.organization?.type === 'SELLER' || user?.organization?.type === 'FULFILLMENT') && (
+ {/* Мои поставки - для селлеров */}
+ {user?.organization?.type === 'SELLER' && (
+ )}
+
+ {/* Входящие поставки - для фулфилмент */}
+ {user?.organization?.type === 'FULFILLMENT' && (
+
+ )}
+
+ {/* Отгрузки - для оптовиков */}
+ {user?.organization?.type === 'WHOLESALE' && (
+
+ )}
+
+ {/* Перевозки - для логистов */}
+ {user?.organization?.type === 'LOGIST' && (
+
)}
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)}
+
+
+
+
+
+
Селлер
+
{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 (
+
+
+
+
+ {/* Заголовок */}
+
+
Поставки
+
+ Управление поставками товаров и расходников
+
+
+
+ {/* Основная навигация */}
+
+
+
+
+
+ Поставки товаров
+
+
+
+ Поставки расходников
+
+
+
+ {/* Поставки товаров */}
+
+
+
+
+
+ Наш фулфилмент
+
+
+
+ Маркетплейсы
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Поставки расходников */}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/logistics/logistics-dashboard.tsx b/src/components/logistics/logistics-dashboard.tsx
new file mode 100644
index 0000000..fa1a76b
--- /dev/null
+++ b/src/components/logistics/logistics-dashboard.tsx
@@ -0,0 +1,285 @@
+"use client";
+
+import { useState } from "react";
+import { Card } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { Badge } from "@/components/ui/badge";
+import { Sidebar } from "@/components/dashboard/sidebar";
+import { useSidebar } from "@/hooks/useSidebar";
+import {
+ Truck,
+ Plus,
+ MapPin,
+ Clock,
+ Package,
+ TrendingUp,
+ AlertTriangle,
+ Navigation,
+} from "lucide-react";
+
+// Мок данные для перевозок
+const mockLogistics = [
+ {
+ id: "1",
+ routeNumber: "LOG-001",
+ from: "Садовод",
+ fromAddress: "Москва, 14-й км МКАД",
+ to: "SFERAV Logistics",
+ toAddress: "Москва, ул. Складская, 15",
+ status: "in_transit",
+ distance: "45 км",
+ estimatedTime: "1 ч 30 мин",
+ cargo: "Смартфоны (50 шт.)",
+ price: 15000,
+ createdDate: "2024-01-10",
+ },
+ {
+ id: "2",
+ routeNumber: "LOG-002",
+ from: "SFERAV Logistics",
+ fromAddress: "Москва, ул. Складская, 15",
+ to: "Коледино WB",
+ toAddress: "МО, г. Подольск, Коледино",
+ status: "delivered",
+ distance: "62 км",
+ estimatedTime: "2 ч 15 мин",
+ cargo: "Одежда (120 шт.)",
+ price: 22000,
+ createdDate: "2024-01-09",
+ },
+ {
+ id: "3",
+ routeNumber: "LOG-003",
+ from: "Тверь Ozon",
+ fromAddress: "г. Тверь, ул. Складская, 88",
+ to: "SFERAV Logistics",
+ toAddress: "Москва, ул. Складская, 15",
+ status: "planned",
+ distance: "178 км",
+ estimatedTime: "3 ч 45 мин",
+ cargo: "Электроника (75 шт.)",
+ price: 35000,
+ createdDate: "2024-01-11",
+ },
+];
+
+export function LogisticsDashboard() {
+ const { getSidebarMargin } = useSidebar();
+
+ 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: "Доставлено",
+ },
+ cancelled: { color: "text-red-300 border-red-400/30", label: "Отменено" },
+ };
+
+ const config =
+ statusConfig[status as keyof typeof statusConfig] || statusConfig.planned;
+
+ return (
+
+ {config.label}
+
+ );
+ };
+
+ const getTotalRevenue = () => {
+ return mockLogistics.reduce((sum, route) => sum + route.price, 0);
+ };
+
+ const getInTransitCount = () => {
+ return mockLogistics.filter((route) => route.status === "in_transit")
+ .length;
+ };
+
+ const getDeliveredCount = () => {
+ return mockLogistics.filter((route) => route.status === "delivered").length;
+ };
+
+ return (
+
+
+
+
+ {/* Заголовок */}
+
+
+
Перевозки
+
+ Управление логистическими маршрутами
+
+
+
+
+
+ {/* Статистика */}
+
+
+
+
+
+
+
+
Всего маршрутов
+
+ {mockLogistics.length}
+
+
+
+
+
+
+
+
+
+
+
+
В пути
+
+ {getInTransitCount()}
+
+
+
+
+
+
+
+
+
+
Доставлено
+
+ {getDeliveredCount()}
+
+
+
+
+
+
+
+
+
+
+
+
Выручка
+
+ {formatCurrency(getTotalRevenue())}
+
+
+
+
+
+
+ {/* Список маршрутов */}
+
+
+
+ Активные маршруты
+
+
+ {mockLogistics.map((route) => (
+
+
+
+
+
+ {route.routeNumber}
+
+ {getStatusBadge(route.status)}
+
+
+
+
+
+
+
+
+ {route.from}
+
+
+ {route.fromAddress}
+
+
+
+
+
+
+
+ {route.to}
+
+
+ {route.toAddress}
+
+
+
+
+
+
+
+
+
+
+ {route.distance} • {route.estimatedTime}
+
+
+
+
+
+
+
+ Создано: {formatDate(route.createdDate)}
+
+
+ {formatCurrency(route.price)}
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}