Добавлен новый компонент для отображения бизнес-процессов в интерфейсе управления. Обновлен компонент UIKitSection для интеграции нового демо и улучшения навигации. Оптимизирована логика отображения данных и улучшена читаемость кода. Исправлены текстовые метки для повышения удобства использования.
This commit is contained in:
@ -4,6 +4,7 @@ import React, { useState } from "react";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
@ -61,9 +62,12 @@ interface SupplyOrder {
|
||||
|
||||
export function SuppliesConsumablesTab() {
|
||||
const [expandedOrders, setExpandedOrders] = useState<Set<string>>(new Set());
|
||||
const { user } = useAuth();
|
||||
|
||||
// Загружаем заказы поставок
|
||||
const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS);
|
||||
const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS, {
|
||||
fetchPolicy: "cache-and-network", // Всегда проверяем актуальные данные
|
||||
});
|
||||
|
||||
const toggleOrderExpansion = (orderId: string) => {
|
||||
const newExpanded = new Set(expandedOrders);
|
||||
@ -75,8 +79,11 @@ export function SuppliesConsumablesTab() {
|
||||
setExpandedOrders(newExpanded);
|
||||
};
|
||||
|
||||
// Получаем данные заказов поставок
|
||||
const supplyOrders: SupplyOrder[] = data?.supplyOrders || [];
|
||||
// Получаем данные заказов поставок и фильтруем только заказы созданные текущим селлером
|
||||
const allSupplyOrders: SupplyOrder[] = data?.supplyOrders || [];
|
||||
const supplyOrders: SupplyOrder[] = allSupplyOrders.filter(
|
||||
(order) => order.organization.id === user?.organization?.id
|
||||
);
|
||||
|
||||
// Генерируем порядковые номера для заказов
|
||||
const ordersWithNumbers = supplyOrders.map((order, index) => ({
|
||||
|
@ -18,13 +18,11 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { apolloClient } from "@/lib/apollo-client";
|
||||
import { GET_MY_COUNTERPARTIES, GET_ORGANIZATION_LOGISTICS } from "@/graphql/queries";
|
||||
import {
|
||||
ArrowLeft,
|
||||
Package,
|
||||
CalendarIcon,
|
||||
Building,
|
||||
} from "lucide-react";
|
||||
GET_MY_COUNTERPARTIES,
|
||||
GET_ORGANIZATION_LOGISTICS,
|
||||
} from "@/graphql/queries";
|
||||
import { ArrowLeft, Package, CalendarIcon, Building } from "lucide-react";
|
||||
|
||||
// Компонент создания поставки товаров с новым интерфейсом
|
||||
|
||||
@ -47,16 +45,18 @@ export function CreateSupplyPage() {
|
||||
const [goodsVolume, setGoodsVolume] = useState<number>(0);
|
||||
const [cargoPlaces, setCargoPlaces] = useState<number>(0);
|
||||
const [goodsPrice, setGoodsPrice] = useState<number>(0);
|
||||
const [fulfillmentServicesPrice, setFulfillmentServicesPrice] = useState<number>(0);
|
||||
const [fulfillmentServicesPrice, setFulfillmentServicesPrice] =
|
||||
useState<number>(0);
|
||||
const [logisticsPrice, setLogisticsPrice] = useState<number>(0);
|
||||
const [selectedServicesCost, setSelectedServicesCost] = useState<number>(0);
|
||||
const [selectedConsumablesCost, setSelectedConsumablesCost] = useState<number>(0);
|
||||
const [selectedConsumablesCost, setSelectedConsumablesCost] =
|
||||
useState<number>(0);
|
||||
const [hasItemsInSupply, setHasItemsInSupply] = useState<boolean>(false);
|
||||
|
||||
// Загружаем контрагентов-фулфилментов
|
||||
const { data: counterpartiesData } = useQuery(GET_MY_COUNTERPARTIES);
|
||||
|
||||
// Фильтруем только фулфилмент организации
|
||||
// Фильтруем только фулфилмент организации
|
||||
const fulfillmentOrgs = (counterpartiesData?.myCounterparties || []).filter(
|
||||
(org: Organization) => org.type === "FULFILLMENT"
|
||||
);
|
||||
@ -87,19 +87,28 @@ export function CreateSupplyPage() {
|
||||
};
|
||||
|
||||
// Функция для обновления информации о поставщиках (для расчета логистики)
|
||||
const handleSuppliersUpdate = (suppliersData: any[]) => {
|
||||
const handleSuppliersUpdate = (suppliersData: unknown[]) => {
|
||||
// Находим рынок из выбранного поставщика
|
||||
const selectedSupplier = suppliersData.find(supplier => supplier.selected);
|
||||
const supplierMarket = selectedSupplier?.market;
|
||||
|
||||
console.log("Обновление поставщиков:", { selectedSupplier, supplierMarket, volume: goodsVolume });
|
||||
|
||||
const selectedSupplier = suppliersData.find(
|
||||
(supplier: unknown) => (supplier as { selected?: boolean }).selected
|
||||
);
|
||||
const supplierMarket = (selectedSupplier as { market?: string })?.market;
|
||||
|
||||
console.log("Обновление поставщиков:", {
|
||||
selectedSupplier,
|
||||
supplierMarket,
|
||||
volume: goodsVolume,
|
||||
});
|
||||
|
||||
// Пересчитываем логистику с учетом рынка поставщика
|
||||
calculateLogisticsPrice(goodsVolume, supplierMarket);
|
||||
};
|
||||
|
||||
// Функция для расчета логистики по рынку поставщика и объему
|
||||
const calculateLogisticsPrice = async (volume: number, supplierMarket?: string) => {
|
||||
const calculateLogisticsPrice = async (
|
||||
volume: number,
|
||||
supplierMarket?: string
|
||||
) => {
|
||||
// Логистика рассчитывается ТОЛЬКО если есть:
|
||||
// 1. Выбранный фулфилмент
|
||||
// 2. Объем товаров > 0
|
||||
@ -110,22 +119,35 @@ export function CreateSupplyPage() {
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`Расчет логистики: ${supplierMarket} → ${selectedFulfillment}, объем: ${volume.toFixed(4)} м³`);
|
||||
|
||||
console.log(
|
||||
`Расчет логистики: ${supplierMarket} → ${selectedFulfillment}, объем: ${volume.toFixed(
|
||||
4
|
||||
)} м³`
|
||||
);
|
||||
|
||||
// Получаем логистику выбранного фулфилмента из БД
|
||||
const { data: logisticsData } = await apolloClient.query({
|
||||
query: GET_ORGANIZATION_LOGISTICS,
|
||||
variables: { organizationId: selectedFulfillment },
|
||||
fetchPolicy: 'network-only'
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const logistics = logisticsData?.organizationLogistics || [];
|
||||
console.log(`Логистика фулфилмента ${selectedFulfillment}:`, logistics);
|
||||
|
||||
// Ищем логистику для данного рынка
|
||||
const logisticsRoute = logistics.find((route: any) =>
|
||||
route.fromLocation.toLowerCase().includes(supplierMarket.toLowerCase()) ||
|
||||
supplierMarket.toLowerCase().includes(route.fromLocation.toLowerCase())
|
||||
const logisticsRoute = logistics.find(
|
||||
(route: {
|
||||
fromLocation: string;
|
||||
toLocation: string;
|
||||
pricePerCubicMeter: number;
|
||||
}) =>
|
||||
route.fromLocation
|
||||
.toLowerCase()
|
||||
.includes(supplierMarket.toLowerCase()) ||
|
||||
supplierMarket
|
||||
.toLowerCase()
|
||||
.includes(route.fromLocation.toLowerCase())
|
||||
);
|
||||
|
||||
if (!logisticsRoute) {
|
||||
@ -135,12 +157,21 @@ export function CreateSupplyPage() {
|
||||
}
|
||||
|
||||
// Выбираем цену в зависимости от объема
|
||||
const pricePerM3 = volume <= 1 ? logisticsRoute.priceUnder1m3 : logisticsRoute.priceOver1m3;
|
||||
const pricePerM3 =
|
||||
volume <= 1
|
||||
? logisticsRoute.priceUnder1m3
|
||||
: logisticsRoute.priceOver1m3;
|
||||
const calculatedPrice = volume * pricePerM3;
|
||||
|
||||
console.log(`Найдена логистика: ${logisticsRoute.fromLocation} → ${logisticsRoute.toLocation}`);
|
||||
console.log(`Цена: ${pricePerM3}₽/м³ (${volume <= 1 ? 'до 1м³' : 'больше 1м³'}) × ${volume.toFixed(4)}м³ = ${calculatedPrice.toFixed(2)}₽`);
|
||||
|
||||
|
||||
console.log(
|
||||
`Найдена логистика: ${logisticsRoute.fromLocation} → ${logisticsRoute.toLocation}`
|
||||
);
|
||||
console.log(
|
||||
`Цена: ${pricePerM3}₽/м³ (${
|
||||
volume <= 1 ? "до 1м³" : "больше 1м³"
|
||||
}) × ${volume.toFixed(4)}м³ = ${calculatedPrice.toFixed(2)}₽`
|
||||
);
|
||||
|
||||
setLogisticsPrice(calculatedPrice);
|
||||
} catch (error) {
|
||||
console.error("Error calculating logistics price:", error);
|
||||
@ -149,7 +180,12 @@ export function CreateSupplyPage() {
|
||||
};
|
||||
|
||||
const getTotalSum = () => {
|
||||
return goodsPrice + selectedServicesCost + selectedConsumablesCost + logisticsPrice;
|
||||
return (
|
||||
goodsPrice +
|
||||
selectedServicesCost +
|
||||
selectedConsumablesCost +
|
||||
logisticsPrice
|
||||
);
|
||||
};
|
||||
|
||||
const handleSupplyComplete = () => {
|
||||
@ -258,7 +294,7 @@ export function CreateSupplyPage() {
|
||||
<Select
|
||||
value={selectedFulfillment}
|
||||
onValueChange={(value) => {
|
||||
console.log('Выбран фулфилмент:', value);
|
||||
console.log("Выбран фулфилмент:", value);
|
||||
setSelectedFulfillment(value);
|
||||
}}
|
||||
>
|
||||
@ -282,7 +318,9 @@ export function CreateSupplyPage() {
|
||||
</Label>
|
||||
<div className="h-8 bg-white/10 border border-white/20 rounded-lg flex items-center px-3">
|
||||
<span className="text-white/80 text-xs">
|
||||
{goodsVolume > 0 ? `${goodsVolume.toFixed(2)} м³` : 'Рассчитывается автоматически'}
|
||||
{goodsVolume > 0
|
||||
? `${goodsVolume.toFixed(2)} м³`
|
||||
: "Рассчитывается автоматически"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -295,7 +333,9 @@ export function CreateSupplyPage() {
|
||||
<Input
|
||||
type="number"
|
||||
value={cargoPlaces || ""}
|
||||
onChange={(e) => setCargoPlaces(parseInt(e.target.value) || 0)}
|
||||
onChange={(e) =>
|
||||
setCargoPlaces(parseInt(e.target.value) || 0)
|
||||
}
|
||||
placeholder="шт"
|
||||
className="h-8 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs"
|
||||
/>
|
||||
@ -311,7 +351,9 @@ export function CreateSupplyPage() {
|
||||
</Label>
|
||||
<div className="h-8 bg-white/10 border border-white/20 rounded-lg flex items-center px-3">
|
||||
<span className="text-white/80 text-xs font-medium">
|
||||
{goodsPrice > 0 ? formatCurrency(goodsPrice) : 'Рассчитывается автоматически'}
|
||||
{goodsPrice > 0
|
||||
? formatCurrency(goodsPrice)
|
||||
: "Рассчитывается автоматически"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -323,7 +365,9 @@ export function CreateSupplyPage() {
|
||||
</Label>
|
||||
<div className="h-8 bg-green-500/20 border border-green-400/30 rounded-lg flex items-center px-3">
|
||||
<span className="text-green-400 text-xs font-medium">
|
||||
{selectedServicesCost > 0 ? formatCurrency(selectedServicesCost) : 'Выберите услуги'}
|
||||
{selectedServicesCost > 0
|
||||
? formatCurrency(selectedServicesCost)
|
||||
: "Выберите услуги"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -335,7 +379,9 @@ export function CreateSupplyPage() {
|
||||
</Label>
|
||||
<div className="h-8 bg-orange-500/20 border border-orange-400/30 rounded-lg flex items-center px-3">
|
||||
<span className="text-orange-400 text-xs font-medium">
|
||||
{selectedConsumablesCost > 0 ? formatCurrency(selectedConsumablesCost) : 'Выберите расходники'}
|
||||
{selectedConsumablesCost > 0
|
||||
? formatCurrency(selectedConsumablesCost)
|
||||
: "Выберите расходники"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -347,12 +393,12 @@ export function CreateSupplyPage() {
|
||||
</Label>
|
||||
<div className="h-8 bg-blue-500/20 border border-blue-400/30 rounded-lg flex items-center px-3">
|
||||
<span className="text-blue-400 text-xs font-medium">
|
||||
{logisticsPrice > 0 ? formatCurrency(logisticsPrice) : 'Выберите поставщика'}
|
||||
{logisticsPrice > 0
|
||||
? formatCurrency(logisticsPrice)
|
||||
: "Выберите поставщика"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{/* 9. Итоговая сумма */}
|
||||
@ -370,11 +416,21 @@ export function CreateSupplyPage() {
|
||||
{/* 10. Кнопка создания поставки */}
|
||||
<Button
|
||||
onClick={handleCreateSupplyClick}
|
||||
disabled={!canCreateSupply || isCreatingSupply || !deliveryDate || !selectedFulfillment || !hasItemsInSupply}
|
||||
disabled={
|
||||
!canCreateSupply ||
|
||||
isCreatingSupply ||
|
||||
!deliveryDate ||
|
||||
!selectedFulfillment ||
|
||||
!hasItemsInSupply
|
||||
}
|
||||
className={`w-full h-12 text-sm font-medium transition-all duration-200 ${
|
||||
canCreateSupply && deliveryDate && selectedFulfillment && hasItemsInSupply && !isCreatingSupply
|
||||
? 'bg-gradient-to-r from-purple-500 to-blue-500 hover:from-purple-600 hover:to-blue-600 text-white'
|
||||
: 'bg-gray-500/20 text-gray-400 cursor-not-allowed'
|
||||
canCreateSupply &&
|
||||
deliveryDate &&
|
||||
selectedFulfillment &&
|
||||
hasItemsInSupply &&
|
||||
!isCreatingSupply
|
||||
? "bg-gradient-to-r from-purple-500 to-blue-500 hover:from-purple-600 hover:to-blue-600 text-white"
|
||||
: "bg-gray-500/20 text-gray-400 cursor-not-allowed"
|
||||
}`}
|
||||
>
|
||||
{isCreatingSupply ? (
|
||||
@ -383,7 +439,7 @@ export function CreateSupplyPage() {
|
||||
<span>Создание...</span>
|
||||
</div>
|
||||
) : (
|
||||
'Создать поставку'
|
||||
"Создать поставку"
|
||||
)}
|
||||
</Button>
|
||||
</Card>
|
||||
|
@ -113,7 +113,7 @@ interface DirectSupplyCreationProps {
|
||||
onItemsCountChange?: (hasItems: boolean) => void;
|
||||
onConsumablesCostChange?: (cost: number) => void;
|
||||
onVolumeChange?: (totalVolume: number) => void;
|
||||
onSuppliersChange?: (suppliers: any[]) => void;
|
||||
onSuppliersChange?: (suppliers: unknown[]) => void;
|
||||
}
|
||||
|
||||
export function DirectSupplyCreation({
|
||||
@ -888,12 +888,12 @@ export function DirectSupplyCreation({
|
||||
|
||||
// Проверяем есть ли уже выбранные поставщики и уведомляем родителя
|
||||
if (onSuppliersChange && supplyItems.length > 0) {
|
||||
const suppliersInfo = suppliersData.supplySuppliers.map((supplier: any) => ({
|
||||
const suppliersInfo = suppliersData.supplySuppliers.map((supplier: { id: string; selected?: boolean }) => ({
|
||||
...supplier,
|
||||
selected: supplyItems.some(item => item.supplierId === supplier.id)
|
||||
}));
|
||||
|
||||
if (suppliersInfo.some((s: any) => s.selected)) {
|
||||
if (suppliersInfo.some((s: { selected?: boolean }) => s.selected)) {
|
||||
console.log("Найдены выбранные поставщики при загрузке:", suppliersInfo);
|
||||
|
||||
// Вызываем асинхронно чтобы не обновлять состояние во время рендера
|
||||
|
@ -4,7 +4,9 @@ import React, { useState, useEffect } from "react";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { FulfillmentGoodsTab } from "./fulfillment-goods-tab";
|
||||
import { RealSupplyOrdersTab } from "./real-supply-orders-tab";
|
||||
import { SellerSupplyOrdersTab } from "./seller-supply-orders-tab";
|
||||
import { PvzReturnsTab } from "./pvz-returns-tab";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
|
||||
interface FulfillmentSuppliesTabProps {
|
||||
defaultSubTab?: string;
|
||||
@ -14,6 +16,7 @@ export function FulfillmentSuppliesTab({
|
||||
defaultSubTab,
|
||||
}: FulfillmentSuppliesTabProps) {
|
||||
const [activeSubTab, setActiveSubTab] = useState("goods");
|
||||
const { user } = useAuth();
|
||||
|
||||
// Устанавливаем активную подвкладку при получении defaultSubTab
|
||||
useEffect(() => {
|
||||
@ -22,6 +25,9 @@ export function FulfillmentSuppliesTab({
|
||||
}
|
||||
}, [defaultSubTab]);
|
||||
|
||||
// Определяем тип организации для выбора правильного компонента
|
||||
const isWholesale = user?.organization?.type === "WHOLESALE";
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-hidden">
|
||||
<Tabs
|
||||
@ -57,7 +63,7 @@ export function FulfillmentSuppliesTab({
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="supplies" className="mt-0 flex-1 overflow-hidden">
|
||||
<RealSupplyOrdersTab />
|
||||
{isWholesale ? <RealSupplyOrdersTab /> : <SellerSupplyOrdersTab />}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="returns" className="mt-0 flex-1 overflow-hidden">
|
||||
|
@ -3,18 +3,28 @@
|
||||
import React, { useState } from "react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { StatsCard } from "../ui/stats-card";
|
||||
import { StatsGrid } from "../ui/stats-grid";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import { GET_SUPPLY_ORDERS } from "@/graphql/queries";
|
||||
import { UPDATE_SUPPLY_ORDER_STATUS } from "@/graphql/mutations";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Calendar,
|
||||
MapPin,
|
||||
Building2,
|
||||
TrendingUp,
|
||||
DollarSign,
|
||||
Wrench,
|
||||
Package2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
User,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Clock,
|
||||
Truck,
|
||||
} from "lucide-react";
|
||||
|
||||
interface SupplyOrderItem {
|
||||
@ -36,10 +46,12 @@ interface SupplyOrderItem {
|
||||
|
||||
interface SupplyOrder {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
deliveryDate: string;
|
||||
status: string;
|
||||
status: "PENDING" | "CONFIRMED" | "IN_TRANSIT" | "DELIVERED" | "CANCELLED";
|
||||
totalAmount: number;
|
||||
totalItems: number;
|
||||
fulfillmentCenterId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
partner: {
|
||||
@ -57,15 +69,53 @@ interface SupplyOrder {
|
||||
fullName?: string;
|
||||
type: string;
|
||||
};
|
||||
fulfillmentCenter?: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
type: string;
|
||||
};
|
||||
items: SupplyOrderItem[];
|
||||
}
|
||||
|
||||
export function RealSupplyOrdersTab() {
|
||||
const [expandedOrders, setExpandedOrders] = useState<Set<string>>(new Set());
|
||||
const { user } = useAuth();
|
||||
|
||||
const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS);
|
||||
const { data, loading, error, refetch } = useQuery(GET_SUPPLY_ORDERS, {
|
||||
fetchPolicy: "cache-and-network",
|
||||
notifyOnNetworkStatusChange: true,
|
||||
});
|
||||
|
||||
const supplyOrders: SupplyOrder[] = data?.supplyOrders || [];
|
||||
// Мутация для обновления статуса заказа
|
||||
const [updateSupplyOrderStatus, { loading: updating }] = useMutation(
|
||||
UPDATE_SUPPLY_ORDER_STATUS,
|
||||
{
|
||||
onCompleted: (data) => {
|
||||
if (data.updateSupplyOrderStatus.success) {
|
||||
toast.success(data.updateSupplyOrderStatus.message);
|
||||
refetch(); // Обновляем список заказов
|
||||
} else {
|
||||
toast.error(data.updateSupplyOrderStatus.message);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error updating supply order status:", error);
|
||||
toast.error("Ошибка при обновлении статуса заказа");
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Получаем ID текущей организации (оптовика)
|
||||
const currentOrganizationId = user?.organization?.id;
|
||||
|
||||
// Фильтруем заказы где текущая организация является поставщиком (заказы К оптовику)
|
||||
const incomingSupplyOrders: SupplyOrder[] = (data?.supplyOrders || []).filter(
|
||||
(order: SupplyOrder) => {
|
||||
// Показываем заказы где текущий оптовик указан как поставщик (partnerId)
|
||||
return order.partner.id === currentOrganizationId;
|
||||
}
|
||||
);
|
||||
|
||||
const toggleOrderExpansion = (orderId: string) => {
|
||||
const newExpanded = new Set(expandedOrders);
|
||||
@ -77,36 +127,59 @@ export function RealSupplyOrdersTab() {
|
||||
setExpandedOrders(newExpanded);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusMap: Record<string, { label: string; color: string }> = {
|
||||
CREATED: {
|
||||
label: "Создан",
|
||||
const handleStatusUpdate = async (
|
||||
orderId: string,
|
||||
newStatus: SupplyOrder["status"]
|
||||
) => {
|
||||
try {
|
||||
await updateSupplyOrderStatus({
|
||||
variables: {
|
||||
id: orderId,
|
||||
status: newStatus,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating status:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: SupplyOrder["status"]) => {
|
||||
const statusMap = {
|
||||
PENDING: {
|
||||
label: "Ожидает одобрения",
|
||||
color: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||||
icon: Clock,
|
||||
},
|
||||
CONFIRMED: {
|
||||
label: "Подтвержден",
|
||||
label: "Одобрена",
|
||||
color: "bg-green-500/20 text-green-300 border-green-500/30",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
IN_PROGRESS: {
|
||||
label: "В процессе",
|
||||
IN_TRANSIT: {
|
||||
label: "В пути",
|
||||
color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30",
|
||||
icon: Truck,
|
||||
},
|
||||
DELIVERED: {
|
||||
label: "Доставлен",
|
||||
label: "Доставлена",
|
||||
color: "bg-purple-500/20 text-purple-300 border-purple-500/30",
|
||||
icon: Package2,
|
||||
},
|
||||
CANCELLED: {
|
||||
label: "Отменен",
|
||||
label: "Отклонена",
|
||||
color: "bg-red-500/20 text-red-300 border-red-500/30",
|
||||
icon: XCircle,
|
||||
},
|
||||
};
|
||||
|
||||
const { label, color } = statusMap[status] || {
|
||||
label: status,
|
||||
color: "bg-gray-500/20 text-gray-300 border-gray-500/30",
|
||||
};
|
||||
|
||||
return <Badge className={`${color} border`}>{label}</Badge>;
|
||||
|
||||
const { label, color, icon: Icon } = statusMap[status];
|
||||
|
||||
return (
|
||||
<Badge className={`${color} border flex items-center gap-1`}>
|
||||
<Icon className="h-3 w-3" />
|
||||
{label}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
@ -135,17 +208,31 @@ export function RealSupplyOrdersTab() {
|
||||
});
|
||||
};
|
||||
|
||||
// Статистика
|
||||
const totalOrders = supplyOrders.length;
|
||||
const totalAmount = supplyOrders.reduce((sum, order) => sum + order.totalAmount, 0);
|
||||
const totalItems = supplyOrders.reduce((sum, order) => sum + order.totalItems, 0);
|
||||
const completedOrders = supplyOrders.filter(order => order.status === "DELIVERED").length;
|
||||
// Статистика для оптовика
|
||||
const totalOrders = incomingSupplyOrders.length;
|
||||
const totalAmount = incomingSupplyOrders.reduce(
|
||||
(sum, order) => sum + order.totalAmount,
|
||||
0
|
||||
);
|
||||
const totalItems = incomingSupplyOrders.reduce(
|
||||
(sum, order) => sum + order.totalItems,
|
||||
0
|
||||
);
|
||||
const pendingOrders = incomingSupplyOrders.filter(
|
||||
(order) => order.status === "PENDING"
|
||||
).length;
|
||||
const approvedOrders = incomingSupplyOrders.filter(
|
||||
(order) => order.status === "CONFIRMED"
|
||||
).length;
|
||||
const inTransitOrders = incomingSupplyOrders.filter(
|
||||
(order) => order.status === "IN_TRANSIT"
|
||||
).length;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-2 border-white border-t-transparent"></div>
|
||||
<span className="ml-3 text-white/60">Загрузка заказов расходников...</span>
|
||||
<span className="ml-3 text-white/60">Загрузка заявок...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -155,7 +242,7 @@ export function RealSupplyOrdersTab() {
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<Wrench className="h-12 w-12 text-red-400 mx-auto mb-4" />
|
||||
<p className="text-red-400 font-medium">Ошибка загрузки заказов</p>
|
||||
<p className="text-red-400 font-medium">Ошибка загрузки заявок</p>
|
||||
<p className="text-white/60 text-sm mt-2">{error.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -163,16 +250,43 @@ export function RealSupplyOrdersTab() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full flex flex-col space-y-6">
|
||||
{/* Статистика заказов расходников */}
|
||||
<div className="space-y-6">
|
||||
{/* Статистика входящих заявок */}
|
||||
<StatsGrid>
|
||||
<StatsCard
|
||||
title="Всего заказов"
|
||||
title="Всего заявок"
|
||||
value={totalOrders}
|
||||
icon={Package2}
|
||||
iconColor="text-orange-400"
|
||||
iconBg="bg-orange-500/20"
|
||||
subtitle="Заказы расходников"
|
||||
subtitle="Заявки от селлеров"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Ожидают одобрения"
|
||||
value={pendingOrders}
|
||||
icon={Clock}
|
||||
iconColor="text-blue-400"
|
||||
iconBg="bg-blue-500/20"
|
||||
subtitle="Требуют решения"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Одобрено"
|
||||
value={approvedOrders}
|
||||
icon={CheckCircle}
|
||||
iconColor="text-green-400"
|
||||
iconBg="bg-green-500/20"
|
||||
subtitle="Готовы к отправке"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="В пути"
|
||||
value={inTransitOrders}
|
||||
icon={Truck}
|
||||
iconColor="text-yellow-400"
|
||||
iconBg="bg-yellow-500/20"
|
||||
subtitle="Доставляются"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
@ -181,56 +295,47 @@ export function RealSupplyOrdersTab() {
|
||||
icon={TrendingUp}
|
||||
iconColor="text-green-400"
|
||||
iconBg="bg-green-500/20"
|
||||
subtitle="Стоимость заказов"
|
||||
subtitle="Стоимость заявок"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Всего единиц"
|
||||
value={totalItems}
|
||||
icon={Wrench}
|
||||
iconColor="text-blue-400"
|
||||
iconBg="bg-blue-500/20"
|
||||
subtitle="Количество расходников"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Завершено"
|
||||
value={completedOrders}
|
||||
icon={Calendar}
|
||||
iconColor="text-purple-400"
|
||||
iconBg="bg-purple-500/20"
|
||||
subtitle="Доставленные заказы"
|
||||
subtitle="Количество товаров"
|
||||
/>
|
||||
</StatsGrid>
|
||||
|
||||
{/* Список заказов расходников */}
|
||||
{supplyOrders.length === 0 ? (
|
||||
{/* Список входящих заявок */}
|
||||
{incomingSupplyOrders.length === 0 ? (
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-8">
|
||||
<div className="text-center">
|
||||
<Wrench className="h-16 w-16 text-white/20 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-white mb-2">
|
||||
Пока нет заказов расходников
|
||||
Пока нет заявок
|
||||
</h3>
|
||||
<p className="text-white/60">
|
||||
Создайте первый заказ расходников через кнопку "Создать поставку"
|
||||
Здесь будут отображаться заявки от селлеров на поставку товаров
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 overflow-hidden flex-1 flex flex-col">
|
||||
<div className="overflow-auto flex-1">
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-white/20">
|
||||
<th className="text-left p-4 text-white font-semibold">ID</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Поставщик
|
||||
Заказчик
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Дата поставки
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Дата создания
|
||||
Дата заявки
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Количество
|
||||
@ -241,34 +346,48 @@ export function RealSupplyOrdersTab() {
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Статус
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Действия
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{supplyOrders.map((order) => {
|
||||
{incomingSupplyOrders.map((order) => {
|
||||
const isOrderExpanded = expandedOrders.has(order.id);
|
||||
|
||||
return (
|
||||
<React.Fragment key={order.id}>
|
||||
{/* Основная строка заказа */}
|
||||
<tr
|
||||
className="border-b border-white/10 hover:bg-white/5 transition-colors cursor-pointer"
|
||||
onClick={() => toggleOrderExpansion(order.id)}
|
||||
>
|
||||
<tr className="border-b border-white/10 hover:bg-white/5 transition-colors">
|
||||
<td className="p-4">
|
||||
<span className="text-white font-medium">
|
||||
{order.id.slice(-8)}
|
||||
</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
<button
|
||||
onClick={() => toggleOrderExpansion(order.id)}
|
||||
className="text-white/60 hover:text-white"
|
||||
>
|
||||
{isOrderExpanded ? (
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
<span className="text-white font-medium">
|
||||
{order.id.slice(-8)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Building2 className="h-4 w-4 text-white/40" />
|
||||
<User className="h-4 w-4 text-white/40" />
|
||||
<span className="text-white font-medium">
|
||||
{order.partner.name || order.partner.fullName || "Поставщик"}
|
||||
{order.organization.name ||
|
||||
order.organization.fullName ||
|
||||
"Заказчик"}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-white/60 text-sm">
|
||||
ИНН: {order.partner.inn}
|
||||
Тип: {order.organization.type}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
@ -299,16 +418,74 @@ export function RealSupplyOrdersTab() {
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">{getStatusBadge(order.status)}</td>
|
||||
<td className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
{order.status === "PENDING" && (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleStatusUpdate(order.id, "CONFIRMED")
|
||||
}
|
||||
disabled={updating}
|
||||
className="bg-green-500/20 hover:bg-green-500/30 text-green-300 border border-green-500/30"
|
||||
>
|
||||
<CheckCircle className="h-4 w-4 mr-1" />
|
||||
Одобрить
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleStatusUpdate(order.id, "CANCELLED")
|
||||
}
|
||||
disabled={updating}
|
||||
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 border border-red-500/30"
|
||||
>
|
||||
<XCircle className="h-4 w-4 mr-1" />
|
||||
Отказать
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{order.status === "CONFIRMED" && (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={() =>
|
||||
handleStatusUpdate(order.id, "IN_TRANSIT")
|
||||
}
|
||||
disabled={updating}
|
||||
className="bg-yellow-500/20 hover:bg-yellow-500/30 text-yellow-300 border border-yellow-500/30"
|
||||
>
|
||||
<Truck className="h-4 w-4 mr-1" />
|
||||
Отправить
|
||||
</Button>
|
||||
)}
|
||||
{order.status === "CANCELLED" && (
|
||||
<span className="text-red-400 text-sm">
|
||||
Отклонена
|
||||
</span>
|
||||
)}
|
||||
{order.status === "IN_TRANSIT" && (
|
||||
<span className="text-yellow-400 text-sm">
|
||||
В пути
|
||||
</span>
|
||||
)}
|
||||
{order.status === "DELIVERED" && (
|
||||
<span className="text-green-400 text-sm">
|
||||
Доставлена
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{/* Развернутая информация о заказе */}
|
||||
{isOrderExpanded && (
|
||||
<tr>
|
||||
<td colSpan={7} className="p-0">
|
||||
<td colSpan={8} className="p-0">
|
||||
<div className="bg-white/5 border-t border-white/10">
|
||||
<div className="p-6">
|
||||
<h4 className="text-white font-semibold mb-4">
|
||||
Состав заказа:
|
||||
Состав заявки:
|
||||
</h4>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{order.items.map((item) => (
|
||||
@ -364,4 +541,4 @@ export function RealSupplyOrdersTab() {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,418 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { GET_SUPPLY_ORDERS } from "@/graphql/queries";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import {
|
||||
Calendar,
|
||||
Building2,
|
||||
TrendingUp,
|
||||
DollarSign,
|
||||
Wrench,
|
||||
Package2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
User,
|
||||
Clock,
|
||||
Truck,
|
||||
Box,
|
||||
} from "lucide-react";
|
||||
|
||||
// Типы данных для заказов поставок расходников
|
||||
interface SupplyOrderItem {
|
||||
id: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
totalPrice: number;
|
||||
product: {
|
||||
id: string;
|
||||
name: string;
|
||||
article?: string;
|
||||
description?: string;
|
||||
category?: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface SupplyOrder {
|
||||
id: string;
|
||||
deliveryDate: string;
|
||||
status: "PENDING" | "CONFIRMED" | "IN_TRANSIT" | "DELIVERED" | "CANCELLED";
|
||||
totalAmount: number;
|
||||
totalItems: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
partner: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
inn?: string;
|
||||
address?: string;
|
||||
phones?: string[];
|
||||
emails?: string[];
|
||||
};
|
||||
organization: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
type: string;
|
||||
};
|
||||
fulfillmentCenter?: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
};
|
||||
items: SupplyOrderItem[];
|
||||
}
|
||||
|
||||
export function SellerSupplyOrdersTab() {
|
||||
const [expandedOrders, setExpandedOrders] = useState<Set<string>>(new Set());
|
||||
const { user } = useAuth();
|
||||
|
||||
// Загружаем заказы поставок
|
||||
const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS, {
|
||||
fetchPolicy: "cache-and-network",
|
||||
});
|
||||
|
||||
const toggleOrderExpansion = (orderId: string) => {
|
||||
const newExpanded = new Set(expandedOrders);
|
||||
if (newExpanded.has(orderId)) {
|
||||
newExpanded.delete(orderId);
|
||||
} else {
|
||||
newExpanded.add(orderId);
|
||||
}
|
||||
setExpandedOrders(newExpanded);
|
||||
};
|
||||
|
||||
// Фильтруем заказы созданные текущим селлером
|
||||
const sellerOrders: SupplyOrder[] = (data?.supplyOrders || []).filter(
|
||||
(order: SupplyOrder) => {
|
||||
return order.organization.id === user?.organization?.id;
|
||||
}
|
||||
);
|
||||
|
||||
const getStatusBadge = (status: SupplyOrder["status"]) => {
|
||||
const statusMap = {
|
||||
PENDING: {
|
||||
label: "Ожидает одобрения",
|
||||
color: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||||
},
|
||||
CONFIRMED: {
|
||||
label: "Одобрена",
|
||||
color: "bg-green-500/20 text-green-300 border-green-500/30",
|
||||
},
|
||||
IN_TRANSIT: {
|
||||
label: "В пути",
|
||||
color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30",
|
||||
},
|
||||
DELIVERED: {
|
||||
label: "Доставлена",
|
||||
color: "bg-purple-500/20 text-purple-300 border-purple-500/30",
|
||||
},
|
||||
CANCELLED: {
|
||||
label: "Отменена",
|
||||
color: "bg-red-500/20 text-red-300 border-red-500/30",
|
||||
},
|
||||
};
|
||||
const { label, color } = statusMap[status];
|
||||
return <Badge className={`${color} border text-xs`}>{label}</Badge>;
|
||||
};
|
||||
|
||||
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 totalOrders = sellerOrders.length;
|
||||
const totalAmount = sellerOrders.reduce(
|
||||
(sum, order) => sum + order.totalAmount,
|
||||
0
|
||||
);
|
||||
const totalItems = sellerOrders.reduce(
|
||||
(sum, order) => sum + order.totalItems,
|
||||
0
|
||||
);
|
||||
const pendingOrders = sellerOrders.filter(
|
||||
(order) => order.status === "PENDING"
|
||||
).length;
|
||||
const approvedOrders = sellerOrders.filter(
|
||||
(order) => order.status === "CONFIRMED"
|
||||
).length;
|
||||
const inTransitOrders = sellerOrders.filter(
|
||||
(order) => order.status === "IN_TRANSIT"
|
||||
).length;
|
||||
const deliveredOrders = sellerOrders.filter(
|
||||
(order) => order.status === "DELIVERED"
|
||||
).length;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-2 border-white border-t-transparent"></div>
|
||||
<span className="ml-3 text-white/60">Загрузка заказов...</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-center">
|
||||
<Wrench className="h-12 w-12 text-red-400 mx-auto mb-4" />
|
||||
<p className="text-red-400 font-medium">Ошибка загрузки заказов</p>
|
||||
<p className="text-white/60 text-sm mt-2">{error.message}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Статистика заказов поставок селлера */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="p-1.5 bg-blue-500/20 rounded-lg">
|
||||
<Package2 className="h-4 w-4 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Всего заказов</p>
|
||||
<p className="text-lg font-bold text-white">{totalOrders}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="p-1.5 bg-green-500/20 rounded-lg">
|
||||
<DollarSign className="h-4 w-4 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Общая сумма</p>
|
||||
<p className="text-lg font-bold text-white">
|
||||
{formatCurrency(totalAmount)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="p-1.5 bg-yellow-500/20 rounded-lg">
|
||||
<Clock className="h-4 w-4 text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Ожидают</p>
|
||||
<p className="text-lg font-bold text-white">{pendingOrders}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="p-1.5 bg-purple-500/20 rounded-lg">
|
||||
<Truck className="h-4 w-4 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Доставлено</p>
|
||||
<p className="text-lg font-bold text-white">{deliveredOrders}</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Таблица заказов поставок */}
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-white/20">
|
||||
<th className="text-left p-3 text-white font-semibold text-sm">
|
||||
№
|
||||
</th>
|
||||
<th className="text-left p-3 text-white font-semibold text-sm">
|
||||
Поставщик
|
||||
</th>
|
||||
<th className="text-left p-3 text-white font-semibold text-sm">
|
||||
Дата доставки
|
||||
</th>
|
||||
<th className="text-left p-3 text-white font-semibold text-sm">
|
||||
Позиций
|
||||
</th>
|
||||
<th className="text-left p-3 text-white font-semibold text-sm">
|
||||
Сумма
|
||||
</th>
|
||||
<th className="text-left p-3 text-white font-semibold text-sm">
|
||||
Фулфилмент
|
||||
</th>
|
||||
<th className="text-left p-3 text-white font-semibold text-sm">
|
||||
Статус
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sellerOrders.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={7} className="text-center py-8">
|
||||
<div className="text-white/60">
|
||||
<Box className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
||||
<p>Заказов поставок пока нет</p>
|
||||
<p className="text-sm mt-1">
|
||||
Создайте первый заказ поставки расходников
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
) : (
|
||||
sellerOrders.map((order, index) => {
|
||||
const isOrderExpanded = expandedOrders.has(order.id);
|
||||
const orderNumber = sellerOrders.length - index;
|
||||
|
||||
return (
|
||||
<React.Fragment key={order.id}>
|
||||
{/* Основная строка заказа поставки */}
|
||||
<tr
|
||||
className="border-b border-white/10 hover:bg-white/5 transition-colors cursor-pointer"
|
||||
onClick={() => toggleOrderExpansion(order.id)}
|
||||
>
|
||||
<td className="p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
{isOrderExpanded ? (
|
||||
<ChevronDown className="h-4 w-4 text-white/60" />
|
||||
) : (
|
||||
<ChevronRight className="h-4 w-4 text-white/60" />
|
||||
)}
|
||||
<span className="text-white font-medium">
|
||||
{orderNumber}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Building2 className="h-4 w-4 text-white/40" />
|
||||
<div>
|
||||
<p className="text-white text-sm font-medium">
|
||||
{order.partner.name ||
|
||||
order.partner.fullName ||
|
||||
"Не указан"}
|
||||
</p>
|
||||
{order.partner.inn && (
|
||||
<p className="text-white/60 text-xs">
|
||||
ИНН: {order.partner.inn}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Calendar className="h-4 w-4 text-white/40" />
|
||||
<span className="text-white text-sm">
|
||||
{formatDate(order.deliveryDate)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<span className="text-white text-sm">
|
||||
{order.totalItems}
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<span className="text-white text-sm font-medium">
|
||||
{formatCurrency(order.totalAmount)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Truck className="h-4 w-4 text-white/40" />
|
||||
<span className="text-white/80 text-sm">
|
||||
{order.fulfillmentCenter?.name ||
|
||||
order.fulfillmentCenter?.fullName ||
|
||||
"Не указан"}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-3">{getStatusBadge(order.status)}</td>
|
||||
</tr>
|
||||
|
||||
{/* Развернутая информация о заказе */}
|
||||
{isOrderExpanded && (
|
||||
<tr>
|
||||
<td colSpan={7} className="p-0">
|
||||
<div className="bg-white/5 border-t border-white/10">
|
||||
<div className="p-4 space-y-3">
|
||||
<h4 className="text-white font-medium text-sm mb-2">
|
||||
Состав заказа:
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{order.items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="flex items-center justify-between bg-white/5 rounded-lg p-3"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<Package2 className="h-4 w-4 text-white/40" />
|
||||
<div>
|
||||
<p className="text-white text-sm font-medium">
|
||||
{item.product.name}
|
||||
</p>
|
||||
{item.product.category && (
|
||||
<p className="text-white/60 text-xs">
|
||||
{item.product.category.name}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-white text-sm">
|
||||
{item.quantity} шт ×{" "}
|
||||
{formatCurrency(item.price)}
|
||||
</p>
|
||||
<p className="text-white/60 text-xs">
|
||||
= {formatCurrency(item.totalPrice)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-between items-center pt-2 border-t border-white/10">
|
||||
<span className="text-white/60 text-sm">
|
||||
Создан: {formatDate(order.createdAt)}
|
||||
</span>
|
||||
<span className="text-white font-medium">
|
||||
Итого: {formatCurrency(order.totalAmount)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user