From 983851361e80c1f5ef4a2150f381e5e90152a755 Mon Sep 17 00:00:00 2001 From: Veronika Smirnova Date: Tue, 22 Jul 2025 13:29:29 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=D1=8B=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BE=D0=BA,=20?= =?UTF-8?q?=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82=D0=BE=D0=B2=20=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D1=89=D0=B8=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2=20=D0=B2=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=20FulfillmentGoodsTab.=20=D0=9E=D0=B1=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=BC=D0=BE=D0=BA-=D0=B4?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BE=D0=BA=20=D1=81=20=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D0=B9=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82?= =?UTF-8?q?=D1=83=D1=80=D0=BE=D0=B9,=20=D0=B2=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20=D0=BE=20=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83?= =?UTF-8?q?=D1=82=D0=B0=D1=85=20=D0=B8=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=B2=D1=89=D0=B8=D0=BA=D0=B0=D1=85.=20=D0=A0=D0=B5=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5=D0=BC=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D0=B2=D0=B5=D1=80=D1=82=D1=8B=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D0=BE=D0=BA?= =?UTF-8?q?,=20=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83=D1=82=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D1=89=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2.=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D1=83=D1=81=D0=BE=D0=B2=20=D0=BC=D0=B0=D1=80?= =?UTF-8?q?=D1=88=D1=80=D1=83=D1=82=D0=BE=D0=B2=20=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D0=B2=D1=89=D0=B8=D0=BA=D0=BE=D0=B2=20=D1=81?= =?UTF-8?q?=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=BD=D0=BE=D0=B2=D1=8B=D1=85=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20Badge.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fulfillment-goods-tab.tsx | 714 +++++++++++++++++- .../fulfillment-supplies-tab.tsx | 24 +- 2 files changed, 691 insertions(+), 47 deletions(-) diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-tab.tsx index b0bf9d2..185907f 100644 --- a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-tab.tsx +++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-goods-tab.tsx @@ -29,6 +29,7 @@ import { ChevronDown, ChevronRight, Store, + Building2, } from "lucide-react"; // Интерфейсы для данных @@ -47,8 +48,66 @@ interface Organization { type: string; } +// Новый интерфейс для поставщика/оптовика +interface Supplier { + id: string; + name: string; + fullName?: string; + inn: string; + phone: string; + email: string; + address: string; + managerName: string; + type: "WHOLESALE" | "SUPPLIER"; + products?: { + id: string; + name: string; + quantity: number; + price: number; + totalValue: number; + }[]; + totalValue: number; + status: "active" | "inactive" | "pending"; +} + +interface Route { + id: string; + routeName: string; + fromAddress: string; + toAddress: string; + distance: number; + estimatedTime: string; + transportType: string; + cost: number; + status: "planned" | "in-transit" | "delivered" | "delayed"; + suppliers: Supplier[]; // Добавляем массив поставщиков +} + +interface Supply { + id: string; + supplyNumber: string; + supplyDate: string; + seller: { + id: string; + name: string; + storeName: string; + managerName: string; + phone: string; + email: string; + inn: string; + }; + itemsQuantity: number; + cargoPlaces: number; + volume: number; + responsibleEmployeeId: string; + logisticsPartnerId: string; + status: string; + totalValue: number; + routes: Route[]; +} + // Мок данные для поставок с новой структурой -const mockFulfillmentSupplies = [ +const mockFulfillmentSupplies: Supply[] = [ { id: "1", supplyNumber: "ФФ-2024-001", @@ -69,6 +128,73 @@ const mockFulfillmentSupplies = [ logisticsPartnerId: "log1", status: "planned", totalValue: 2500000, + routes: [ + { + id: "route1-1", + routeName: "Москва → Подольск (Основной)", + fromAddress: "Москва, ул. Складская, 15", + toAddress: "Подольск, ул. Логистическая, 25", + distance: 45, + estimatedTime: "1ч 20мин", + transportType: "Фура 20т", + cost: 15000, + status: "planned", + suppliers: [ + { + id: "sup1-1", + name: "ООО 'ПромСтрой'", + fullName: "ООО 'ПромСтрой' - Оптовый поставщик", + inn: "7701234567890", + phone: "+7 (495) 111-22-33", + email: "info@prosmstroi.ru", + address: "Москва, ул. Строительная, 10", + managerName: "Иванов Иван", + type: "WHOLESALE", + totalValue: 1000000, + status: "active", + }, + { + id: "sup1-2", + name: "ИП 'СтройМаг'", + fullName: "ИП 'СтройМаг' - Оптовый поставщик", + inn: "7709876543210", + phone: "+7 (495) 999-88-77", + email: "orders@stroymag.ru", + address: "Москва, ул. Магистральная, 5", + managerName: "Петров Петр", + type: "SUPPLIER", + totalValue: 500000, + status: "inactive", + }, + ], + }, + { + id: "route1-2", + routeName: "Подольск → Тула (Резервный)", + fromAddress: "Подольск, ул. Логистическая, 25", + toAddress: "Тула, ул. Промышленная, 8", + distance: 180, + estimatedTime: "3ч 15мин", + transportType: "Газель", + cost: 8500, + status: "planned", + suppliers: [ + { + id: "sup1-3", + name: "ООО 'СтройТорг'", + fullName: "ООО 'СтройТорг' - Оптовый поставщик", + inn: "7701123456789", + phone: "+7 (495) 222-33-44", + email: "sales@stroitorg.ru", + address: "Подольск, ул. Логистическая, 25", + managerName: "Сидоров Сидор", + type: "WHOLESALE", + totalValue: 1500000, + status: "active", + }, + ], + }, + ], }, { id: "2", @@ -90,6 +216,34 @@ const mockFulfillmentSupplies = [ logisticsPartnerId: "log2", status: "in-transit", totalValue: 3750000, + routes: [ + { + id: "route2-1", + routeName: "СПб → Москва (Экспресс)", + fromAddress: "Санкт-Петербург, пр. Обуховской Обороны, 120", + toAddress: "Москва, МКАД 47км", + distance: 635, + estimatedTime: "8ч 45мин", + transportType: "Фура 40т", + cost: 45000, + status: "in-transit", + suppliers: [ + { + id: "sup2-1", + name: "ООО 'ЭлектроТорг'", + fullName: "ООО 'ЭлектроТорг' - Оптовый поставщик", + inn: "7701234567890", + phone: "+7 (495) 333-44-55", + email: "info@elektortorg.ru", + address: "Санкт-Петербург, ул. Электронная, 10", + managerName: "Иванов Иван", + type: "WHOLESALE", + totalValue: 2000000, + status: "active", + }, + ], + }, + ], }, { id: "3", @@ -111,6 +265,60 @@ const mockFulfillmentSupplies = [ logisticsPartnerId: "log1", status: "delivered", totalValue: 2800000, + routes: [ + { + id: "route3-1", + routeName: "Казань → Москва (Основной)", + fromAddress: "Казань, ул. Портовая, 18", + toAddress: "Москва, ул. Складская, 15", + distance: 815, + estimatedTime: "12ч 30мин", + transportType: "Фура 20т", + cost: 38000, + status: "delivered", + suppliers: [ + { + id: "sup3-1", + name: "ООО 'МеталлСтрой'", + fullName: "ООО 'МеталлСтрой' - Оптовый поставщик", + inn: "7701234567890", + phone: "+7 (495) 444-55-66", + email: "sales@metallstroi.ru", + address: "Казань, ул. Портовая, 18", + managerName: "Иванов Иван", + type: "WHOLESALE", + totalValue: 1800000, + status: "active", + }, + ], + }, + { + id: "route3-2", + routeName: "Москва → Тверь (Доставка)", + fromAddress: "Москва, ул. Складская, 15", + toAddress: "Тверь, ул. Вагжанова, 7", + distance: 170, + estimatedTime: "2ч 45мин", + transportType: "Газель", + cost: 12000, + status: "delivered", + suppliers: [ + { + id: "sup3-2", + name: "ИП 'СтройМаг'", + fullName: "ИП 'СтройМаг' - Оптовый поставщик", + inn: "7709876543210", + phone: "+7 (495) 999-88-77", + email: "orders@stroymag.ru", + address: "Москва, ул. Складская, 15", + managerName: "Петров Петр", + type: "SUPPLIER", + totalValue: 1000000, + status: "inactive", + }, + ], + }, + ], }, ]; @@ -120,6 +328,13 @@ export function FulfillmentGoodsTab() { const [expandedSellers, setExpandedSellers] = useState>( new Set() ); + const [expandedSupplies, setExpandedSupplies] = useState>( + new Set() + ); + const [expandedRoutes, setExpandedRoutes] = useState>(new Set()); + const [expandedSuppliers, setExpandedSuppliers] = useState>( + new Set() + ); // Загружаем сотрудников для селектора ответственных const { data: employeesData, loading: employeesLoading } = @@ -145,6 +360,36 @@ export function FulfillmentGoodsTab() { setExpandedSellers(newExpanded); }; + const toggleSupplyExpansion = (supplyId: string) => { + const newExpanded = new Set(expandedSupplies); + if (newExpanded.has(supplyId)) { + newExpanded.delete(supplyId); + } else { + newExpanded.add(supplyId); + } + setExpandedSupplies(newExpanded); + }; + + const toggleRouteExpansion = (routeId: string) => { + const newExpanded = new Set(expandedRoutes); + if (newExpanded.has(routeId)) { + newExpanded.delete(routeId); + } else { + newExpanded.add(routeId); + } + setExpandedRoutes(newExpanded); + }; + + const toggleSupplierExpansion = (supplierId: string) => { + const newExpanded = new Set(expandedSuppliers); + if (newExpanded.has(supplierId)) { + newExpanded.delete(supplierId); + } else { + newExpanded.add(supplierId); + } + setExpandedSuppliers(newExpanded); + }; + const formatCurrency = (amount: number) => { return new Intl.NumberFormat("ru-RU", { style: "currency", @@ -191,6 +436,62 @@ export function FulfillmentGoodsTab() { ); }; + const getRouteStatusBadge = (status: string) => { + const statusConfig = { + planned: { + color: "text-blue-300 border-blue-400/30 bg-blue-500/10", + label: "Запланирован", + }, + "in-transit": { + color: "text-yellow-300 border-yellow-400/30 bg-yellow-500/10", + label: "В пути", + }, + delivered: { + color: "text-green-300 border-green-400/30 bg-green-500/10", + label: "Доставлен", + }, + delayed: { + color: "text-red-300 border-red-400/30 bg-red-500/10", + label: "Задержка", + }, + }; + + const config = + statusConfig[status as keyof typeof statusConfig] || statusConfig.planned; + + return ( + + {config.label} + + ); + }; + + const getSupplierStatusBadge = (status: string) => { + const statusConfig = { + active: { + color: "text-green-300 border-green-400/30 bg-green-500/10", + label: "Активен", + }, + inactive: { + color: "text-gray-300 border-gray-400/30 bg-gray-500/10", + label: "Неактивен", + }, + pending: { + color: "text-yellow-300 border-yellow-400/30 bg-yellow-500/10", + label: "Ожидает", + }, + }; + + const config = + statusConfig[status as keyof typeof statusConfig] || statusConfig.active; + + return ( + + {config.label} + + ); + }; + const getEmployeeName = (employeeId: string) => { const employee = employees.find((emp) => emp.id === employeeId); return employee @@ -336,8 +637,9 @@ export function FulfillmentGoodsTab() { {/* Список поставок */}
- {filteredSupplies.map((supply) => { + {filteredSupplies.map((supply, index) => { const isSellerExpanded = expandedSellers.has(supply.seller.id); + const isSupplyExpanded = expandedSupplies.has(supply.id); return (
- +
+
+ + Номер поставки + + + {supply.supplyNumber} + +
+ +
{/* Скрытая детальная информация о магазине */} @@ -447,14 +759,21 @@ export function FulfillmentGoodsTab() { )} {/* Единый блок со всеми параметрами в одной строке */} -
+
toggleSupplyExpansion(supply.id)} + >
- {/* Номер поставки */} + {/* Порядковый номер */}
-

Номер

-

- {supply.supplyNumber} -

+

+
+
+ + {filteredSupplies.length - index} + +
+
{/* Дата поставки */} @@ -498,13 +817,16 @@ export function FulfillmentGoodsTab() {
{/* Ответственный сотрудник */} -
+
e.stopPropagation()} + >

Ответственный

- + + {employees.map((employee) => ( @@ -528,11 +850,14 @@ export function FulfillmentGoodsTab() {
{/* Логистический партнер */} -
+
e.stopPropagation()} + >

Логистика

- + + {logisticsPartners.map((partner: Organization) => ( @@ -565,6 +890,339 @@ export function FulfillmentGoodsTab() {
+ + {/* Второй уровень - Маршруты */} + {isSupplyExpanded && + supply.routes && + supply.routes.length > 0 && ( +
+
+

+ + Маршруты ({supply.routes.length}) +

+
+
+ {supply.routes.map((route) => { + const isRouteExpanded = expandedRoutes.has( + route.id + ); + return ( +
+
+ toggleRouteExpansion(route.id) + } + > + {/* Название маршрута */} +
+

+ Маршрут +

+
+

+ {route.routeName} +

+ +
+
+ + {/* Откуда */} +
+

+ Откуда +

+

+ {route.fromAddress} +

+
+ + {/* Куда */} +
+

+ Куда +

+

+ {route.toAddress} +

+
+ + {/* Расстояние */} +
+

+ Расстояние +

+

+ {route.distance} км +

+
+ + {/* Время в пути */} +
+

+ Время +

+

+ {route.estimatedTime} +

+
+ + {/* Транспорт */} +
+

+ Транспорт +

+

+ {route.transportType} +

+
+ + {/* Стоимость и статус */} +
+

+ Стоимость +

+

+ {formatCurrency(route.cost)} +

+ {getRouteStatusBadge(route.status)} +
+
+ + {/* Третий уровень - Поставщики/Оптовики */} + {isRouteExpanded && + route.suppliers && + route.suppliers.length > 0 && ( +
+
+
+ + Поставщики/Оптовики ( + {route.suppliers.length}) +
+
+
+ {route.suppliers.map((supplier) => { + const isSupplierExpanded = + expandedSuppliers.has( + supplier.id + ); + return ( +
+
+ toggleSupplierExpansion( + supplier.id + ) + } + > + {/* Название поставщика */} +
+

+ Поставщик +

+
+

+ {supplier.name} +

+ +
+
+ + {/* ИНН */} +
+

+ ИНН +

+

+ {supplier.inn} +

+
+ + {/* Тип */} +
+

+ Тип +

+ + {supplier.type === + "WHOLESALE" + ? "Оптовик" + : "Поставщик"} + +
+ + {/* Менеджер */} +
+

+ Менеджер +

+

+ {supplier.managerName} +

+
+ + {/* Стоимость */} +
+

+ Стоимость +

+

+ {formatCurrency( + supplier.totalValue + )} +

+
+ + {/* Статус */} +
+

+ Статус +

+ {getSupplierStatusBadge( + supplier.status + )} +
+
+ + {/* Детальная информация о поставщике */} + {isSupplierExpanded && ( +
+
+
+

+ Полное название +

+

+ {supplier.fullName || + supplier.name} +

+
+
+

+ Телефон +

+
+

+ {supplier.phone} +

+ +
+
+
+

+ Email +

+

+ {supplier.email} +

+
+
+
+

+ Адрес +

+

+ {supplier.address} +

+
+
+ )} +
+ ); + })} +
+
+ )} +
+ ); + })} +
+
+ )}
diff --git a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx index bd7a576..14d8352 100644 --- a/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx +++ b/src/components/fulfillment-supplies/fulfillment-supplies/fulfillment-supplies-tab.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; -import { Package, Wrench, RotateCcw, Package2, Building2 } from "lucide-react"; +import { Package, Wrench, RotateCcw, Building2 } from "lucide-react"; // Импорты компонентов подкатегорий import { FulfillmentGoodsTab } from "./fulfillment-goods-tab"; @@ -10,7 +10,6 @@ import { PvzReturnsTab } from "./pvz-returns-tab"; import { SuppliesConsumablesTab } from "../../supplies/consumables-supplies/consumables-supplies-tab"; // Новые компоненты для детального просмотра (копия из supplies модуля) -import { FulfillmentDetailedGoodsTab } from "./fulfillment-detailed-goods-tab"; import { FulfillmentDetailedSuppliesTab } from "./fulfillment-detailed-supplies-tab"; export function FulfillmentSuppliesTab() { @@ -23,34 +22,27 @@ export function FulfillmentSuppliesTab() { onValueChange={setActiveTab} className="h-full flex flex-col" > - + - Поставки - - - - Товары детально + Товар - Расходники детально + Расходники (название текущего кабинета) - Расходники С + Расходники селлеров - -
- -
-
-