Добавлен новый компонент для отображения бизнес-процессов в интерфейсе управления. Обновлен компонент UIKitSection для интеграции нового демо и улучшения навигации. Оптимизирована логика отображения данных и улучшена читаемость кода. Исправлены текстовые метки для повышения удобства использования.
This commit is contained in:
@ -1,95 +1,100 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { StatsCard } from "../../supplies/ui/stats-card";
|
||||
import { StatsGrid } from "../../supplies/ui/stats-grid";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { GET_SUPPLY_ORDERS } from "@/graphql/queries";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import { GET_SUPPLY_ORDERS, GET_MY_SUPPLIES } from "@/graphql/queries";
|
||||
import { UPDATE_SUPPLY_ORDER_STATUS } from "@/graphql/mutations";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Calendar,
|
||||
Building2,
|
||||
TrendingUp,
|
||||
DollarSign,
|
||||
Wrench,
|
||||
Package2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
Package,
|
||||
Truck,
|
||||
User,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
AlertCircle,
|
||||
XCircle,
|
||||
MapPin,
|
||||
Phone,
|
||||
Mail,
|
||||
Layers,
|
||||
Building,
|
||||
Hash,
|
||||
Store,
|
||||
} 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;
|
||||
organizationId: string;
|
||||
partnerId: string;
|
||||
deliveryDate: string;
|
||||
status: string;
|
||||
status: "PENDING" | "CONFIRMED" | "IN_TRANSIT" | "DELIVERED" | "CANCELLED";
|
||||
totalAmount: number;
|
||||
totalItems: number;
|
||||
fulfillmentCenterId?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
partner: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
inn: string;
|
||||
name: string;
|
||||
fullName: string;
|
||||
address?: string;
|
||||
phones?: string[];
|
||||
emails?: string[];
|
||||
};
|
||||
organization: {
|
||||
items: Array<{
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
type: string;
|
||||
};
|
||||
fulfillmentCenter?: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
type: string;
|
||||
};
|
||||
items: SupplyOrderItem[];
|
||||
quantity: number;
|
||||
price: number;
|
||||
totalPrice: number;
|
||||
product: {
|
||||
id: string;
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
images?: string[];
|
||||
mainImage?: string;
|
||||
category?: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export function FulfillmentConsumablesOrdersTab() {
|
||||
const [expandedOrders, setExpandedOrders] = useState<Set<string>>(new Set());
|
||||
const { user } = useAuth();
|
||||
|
||||
const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS, {
|
||||
fetchPolicy: 'cache-and-network', // Принудительно проверяем сервер
|
||||
notifyOnNetworkStatusChange: true
|
||||
});
|
||||
// Загружаем заказы поставок
|
||||
const { data, loading, error, refetch } = useQuery(GET_SUPPLY_ORDERS);
|
||||
|
||||
// Получаем ID текущей организации (фулфилмент-центра)
|
||||
const currentOrganizationId = user?.organization?.id;
|
||||
|
||||
// Фильтруем заказы где текущая организация является получателем (заказы ОТ селлеров)
|
||||
const incomingSupplyOrders: SupplyOrder[] = (data?.supplyOrders || []).filter(
|
||||
(order: SupplyOrder) => {
|
||||
// Показываем заказы где текущий фулфилмент-центр указан как получатель
|
||||
// И заказчик НЕ является самим фулфилмент-центром (исключаем наши собственные заказы)
|
||||
return order.fulfillmentCenterId === currentOrganizationId &&
|
||||
order.organizationId !== currentOrganizationId;
|
||||
// Мутация для обновления статуса заказа
|
||||
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);
|
||||
}
|
||||
},
|
||||
refetchQueries: [
|
||||
{ query: GET_SUPPLY_ORDERS }, // Обновляем заказы поставок
|
||||
{ query: GET_MY_SUPPLIES }, // Обновляем склад фулфилмента
|
||||
],
|
||||
onError: (error) => {
|
||||
console.error("Error updating supply order status:", error);
|
||||
toast.error("Ошибка при обновлении статуса заказа");
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
@ -103,44 +108,81 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
setExpandedOrders(newExpanded);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusMap: Record<string, { label: string; color: string }> = {
|
||||
CREATED: {
|
||||
label: "Новый заказ",
|
||||
// Получаем данные заказов поставок
|
||||
const supplyOrders: SupplyOrder[] = data?.supplyOrders || [];
|
||||
|
||||
// Фильтруем заказы для фулфилмента (где текущий фулфилмент является получателем)
|
||||
const fulfillmentOrders = supplyOrders.filter((order) => {
|
||||
// Показываем только заказы где текущий фулфилмент-центр является получателем
|
||||
const isRecipient = order.fulfillmentCenter?.id === user?.organization?.id;
|
||||
// И статус не PENDING и не CANCELLED (одобренные заявки)
|
||||
const isApproved =
|
||||
order.status !== "CANCELLED" && order.status !== "PENDING";
|
||||
|
||||
return isRecipient && isApproved;
|
||||
});
|
||||
|
||||
// Генерируем порядковые номера для заказов
|
||||
const ordersWithNumbers = fulfillmentOrders.map((order, index) => ({
|
||||
...order,
|
||||
number: fulfillmentOrders.length - index, // Обратный порядок для новых заказов сверху
|
||||
}));
|
||||
|
||||
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: Package,
|
||||
},
|
||||
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 text-xs`}>
|
||||
<Icon className="h-3 w-3" />
|
||||
{label}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
currency: "RUB",
|
||||
minimumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
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 canMarkAsDelivered = (status: SupplyOrder["status"]) => {
|
||||
return status === "IN_TRANSIT";
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
@ -151,266 +193,437 @@ export function FulfillmentConsumablesOrdersTab() {
|
||||
});
|
||||
};
|
||||
|
||||
const formatDateTime = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleString("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
currency: "RUB",
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
// Статистика для фулфилмент-центра
|
||||
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 newOrders = incomingSupplyOrders.filter(order => order.status === "CREATED").length;
|
||||
const getInitials = (name: string): string => {
|
||||
return name
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0))
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
};
|
||||
|
||||
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 className="flex items-center justify-center p-8">
|
||||
<div className="text-white/60">Загрузка заказов поставок...</div>
|
||||
</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 className="flex items-center justify-center p-8">
|
||||
<div className="text-red-400">Ошибка загрузки заказов поставок</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Статистика входящих заказов расходников */}
|
||||
<StatsGrid>
|
||||
<StatsCard
|
||||
title="Входящие заказы"
|
||||
value={totalOrders}
|
||||
icon={Package2}
|
||||
iconColor="text-orange-400"
|
||||
iconBg="bg-orange-500/20"
|
||||
subtitle="Заказы от селлеров"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Общая сумма"
|
||||
value={formatCurrency(totalAmount)}
|
||||
icon={TrendingUp}
|
||||
iconColor="text-green-400"
|
||||
iconBg="bg-green-500/20"
|
||||
subtitle="Стоимость заказов"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Всего единиц"
|
||||
value={totalItems}
|
||||
icon={Wrench}
|
||||
iconColor="text-blue-400"
|
||||
iconBg="bg-blue-500/20"
|
||||
subtitle="Количество расходников"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Новые заказы"
|
||||
value={newOrders}
|
||||
icon={Calendar}
|
||||
iconColor="text-purple-400"
|
||||
iconBg="bg-purple-500/20"
|
||||
subtitle="Требуют обработки"
|
||||
/>
|
||||
</StatsGrid>
|
||||
|
||||
{/* Список входящих заказов расходников */}
|
||||
{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 className="space-y-2">
|
||||
{/* Компактная статистика */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2">
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="p-1 bg-blue-500/20 rounded">
|
||||
<Clock className="h-3 w-3 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Ожидание</p>
|
||||
<p className="text-sm font-bold text-white">
|
||||
{
|
||||
fulfillmentOrders.filter(
|
||||
(order) => order.status === "PENDING"
|
||||
).length
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<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">
|
||||
Дата заказа
|
||||
</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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{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)}
|
||||
>
|
||||
<td className="p-4">
|
||||
<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" />
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="p-1 bg-green-500/20 rounded">
|
||||
<CheckCircle className="h-3 w-3 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Подтверждено</p>
|
||||
<p className="text-sm font-bold text-white">
|
||||
{
|
||||
fulfillmentOrders.filter(
|
||||
(order) => order.status === "CONFIRMED"
|
||||
).length
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="p-1 bg-yellow-500/20 rounded">
|
||||
<Truck className="h-3 w-3 text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">В пути</p>
|
||||
<p className="text-sm font-bold text-white">
|
||||
{
|
||||
fulfillmentOrders.filter(
|
||||
(order) => order.status === "IN_TRANSIT"
|
||||
).length
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="p-1 bg-purple-500/20 rounded">
|
||||
<Package className="h-3 w-3 text-purple-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-xs">Доставлено</p>
|
||||
<p className="text-sm font-bold text-white">
|
||||
{
|
||||
fulfillmentOrders.filter(
|
||||
(order) => order.status === "DELIVERED"
|
||||
).length
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Оптимизированный список заказов поставок */}
|
||||
<div className="space-y-1.5">
|
||||
{ordersWithNumbers.length === 0 ? (
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||||
<div className="text-center">
|
||||
<Package className="h-8 w-8 text-white/40 mx-auto mb-2" />
|
||||
<h3 className="text-sm font-semibold text-white mb-1">
|
||||
Нет заказов поставок
|
||||
</h3>
|
||||
<p className="text-white/60 text-xs">
|
||||
Заказы поставок расходников будут отображаться здесь
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
ordersWithNumbers.map((order) => (
|
||||
<Card
|
||||
key={order.id}
|
||||
className="bg-white/10 backdrop-blur border-white/20 overflow-hidden hover:bg-white/15 transition-colors cursor-pointer"
|
||||
onClick={() => toggleOrderExpansion(order.id)}
|
||||
>
|
||||
{/* Компактная основная информация */}
|
||||
<div className="px-3 py-2">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Левая часть - основная информация */}
|
||||
<div className="flex items-center space-x-3 flex-1 min-w-0">
|
||||
{/* Номер поставки */}
|
||||
<div className="flex items-center space-x-1">
|
||||
<Hash className="h-3 w-3 text-white/60 flex-shrink-0" />
|
||||
<span className="text-white font-semibold text-sm">
|
||||
{order.number}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Селлер */}
|
||||
<div className="flex items-center space-x-2 min-w-0">
|
||||
<div className="flex flex-col items-center">
|
||||
<Store className="h-3 w-3 text-blue-400 mb-0.5" />
|
||||
<span className="text-blue-400 text-xs font-medium leading-none">
|
||||
Селлер
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<Avatar className="w-7 h-7 flex-shrink-0">
|
||||
<AvatarFallback className="bg-blue-500 text-white text-xs">
|
||||
{getInitials(
|
||||
order.partner.name || order.partner.fullName
|
||||
)}
|
||||
<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">
|
||||
<User className="h-4 w-4 text-white/40" />
|
||||
<span className="text-white font-medium">
|
||||
{order.organization.name || order.organization.fullName || "Селлер"}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-white/60 text-sm">
|
||||
Тип: {order.organization.type}
|
||||
</p>
|
||||
</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" />
|
||||
<span className="text-white font-medium">
|
||||
{order.partner.name || order.partner.fullName || "Поставщик"}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-white/60 text-sm">
|
||||
ИНН: {order.partner.inn}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Calendar className="h-4 w-4 text-white/40" />
|
||||
<span className="text-white font-semibold">
|
||||
{formatDate(order.deliveryDate)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<span className="text-white/80">
|
||||
{formatDateTime(order.createdAt)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<span className="text-white font-semibold">
|
||||
{order.totalItems} шт
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<DollarSign className="h-4 w-4 text-white/40" />
|
||||
<span className="text-green-400 font-bold">
|
||||
{formatCurrency(order.totalAmount)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">{getStatusBadge(order.status)}</td>
|
||||
</tr>
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="min-w-0 flex-1">
|
||||
<h3 className="text-white font-medium text-sm truncate max-w-[120px]">
|
||||
{order.partner.name || order.partner.fullName}
|
||||
</h3>
|
||||
<p className="text-white/60 text-xs">
|
||||
{order.partner.inn}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Развернутая информация о заказе */}
|
||||
{isOrderExpanded && (
|
||||
<tr>
|
||||
<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) => (
|
||||
<Card
|
||||
key={item.id}
|
||||
className="bg-white/10 backdrop-blur border-white/20 p-4"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<h5 className="text-white font-medium mb-1">
|
||||
{item.product.name}
|
||||
</h5>
|
||||
<p className="text-white/60 text-sm">
|
||||
Артикул: {item.product.article}
|
||||
</p>
|
||||
{item.product.category && (
|
||||
<Badge className="bg-purple-500/20 text-purple-300 border-purple-500/30 text-xs mt-2">
|
||||
{item.product.category.name}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm">
|
||||
<p className="text-white/60">
|
||||
Количество: {item.quantity} шт
|
||||
</p>
|
||||
<p className="text-white/60">
|
||||
Цена: {formatCurrency(item.price)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-green-400 font-semibold">
|
||||
{formatCurrency(item.totalPrice)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
{/* Поставщик (фулфилмент-центр) */}
|
||||
<div className="hidden xl:flex items-center space-x-2 min-w-0">
|
||||
<div className="flex flex-col items-center">
|
||||
<Building className="h-3 w-3 text-green-400 mb-0.5" />
|
||||
<span className="text-green-400 text-xs font-medium leading-none">
|
||||
Поставщик
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<Avatar className="w-7 h-7 flex-shrink-0">
|
||||
<AvatarFallback className="bg-green-500 text-white text-xs">
|
||||
{getInitials(user?.organization?.name || "ФФ")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="min-w-0">
|
||||
<h3 className="text-white font-medium text-sm truncate max-w-[100px]">
|
||||
{user?.organization?.name || "ФФ-центр"}
|
||||
</h3>
|
||||
<p className="text-white/60 text-xs">Наш ФФ</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Краткие данные */}
|
||||
<div className="hidden lg:flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar className="h-3 w-3 text-blue-400" />
|
||||
<span className="text-white text-xs">
|
||||
{formatDate(order.deliveryDate)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Package className="h-3 w-3 text-green-400" />
|
||||
<span className="text-white text-xs">
|
||||
{order.totalItems}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Layers className="h-3 w-3 text-purple-400" />
|
||||
<span className="text-white text-xs">
|
||||
{order.items.length}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Правая часть - статус и действия */}
|
||||
<div className="flex items-center space-x-2 flex-shrink-0">
|
||||
<Badge
|
||||
className={`${
|
||||
order.status === "PENDING"
|
||||
? "bg-blue-500/20 text-blue-300 border-blue-500/30"
|
||||
: order.status === "CONFIRMED"
|
||||
? "bg-green-500/20 text-green-300 border-green-500/30"
|
||||
: order.status === "IN_TRANSIT"
|
||||
? "bg-yellow-500/20 text-yellow-300 border-yellow-500/30"
|
||||
: order.status === "DELIVERED"
|
||||
? "bg-purple-500/20 text-purple-300 border-purple-500/30"
|
||||
: "bg-red-500/20 text-red-300 border-red-500/30"
|
||||
} border flex items-center gap-1 text-xs px-2 py-1`}
|
||||
>
|
||||
{order.status === "PENDING" && (
|
||||
<Clock className="h-3 w-3" />
|
||||
)}
|
||||
{order.status === "CONFIRMED" && (
|
||||
<CheckCircle className="h-3 w-3" />
|
||||
)}
|
||||
{order.status === "IN_TRANSIT" && (
|
||||
<Truck className="h-3 w-3" />
|
||||
)}
|
||||
{order.status === "DELIVERED" && (
|
||||
<Package className="h-3 w-3" />
|
||||
)}
|
||||
{order.status === "CANCELLED" && (
|
||||
<XCircle className="h-3 w-3" />
|
||||
)}
|
||||
{order.status === "PENDING" && "Ожидание"}
|
||||
{order.status === "CONFIRMED" && "Подтверждена"}
|
||||
{order.status === "IN_TRANSIT" && "В пути"}
|
||||
{order.status === "DELIVERED" && "Доставлена"}
|
||||
{order.status === "CANCELLED" && "Отменена"}
|
||||
</Badge>
|
||||
|
||||
{canMarkAsDelivered(order.status) && (
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleStatusUpdate(order.id, "DELIVERED");
|
||||
}}
|
||||
disabled={updating}
|
||||
className="bg-green-500/20 hover:bg-green-500/30 text-green-300 border border-green-500/30 text-xs px-2 py-1 h-7"
|
||||
>
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Получено
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Мобильная версия поставщика и кратких данных */}
|
||||
<div className="xl:hidden mt-2 space-y-1">
|
||||
{/* Поставщик на мобильных */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="flex flex-col items-center">
|
||||
<Building className="h-3 w-3 text-green-400 mb-0.5" />
|
||||
<span className="text-green-400 text-xs font-medium leading-none">
|
||||
Поставщик
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1.5">
|
||||
<Avatar className="w-6 h-6 flex-shrink-0">
|
||||
<AvatarFallback className="bg-green-500 text-white text-xs">
|
||||
{getInitials(user?.organization?.name || "ФФ")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="min-w-0">
|
||||
<h3 className="text-white font-medium text-sm truncate">
|
||||
{user?.organization?.name || "Фулфилмент-центр"}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Краткие данные на мобильных */}
|
||||
<div className="lg:hidden flex items-center justify-between text-xs">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar className="h-3 w-3 text-blue-400" />
|
||||
<span className="text-white">
|
||||
{formatDate(order.deliveryDate)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Package className="h-3 w-3 text-green-400" />
|
||||
<span className="text-white">
|
||||
{order.totalItems} шт.
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Layers className="h-3 w-3 text-purple-400" />
|
||||
<span className="text-white">
|
||||
{order.items.length} поз.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Развернутые детали заказа */}
|
||||
{expandedOrders.has(order.id) && (
|
||||
<>
|
||||
<Separator className="my-2 bg-white/10" />
|
||||
|
||||
{/* Сумма заказа */}
|
||||
<div className="mb-2 p-2 bg-white/5 rounded">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-white/60 text-sm">
|
||||
Общая сумма:
|
||||
</span>
|
||||
<span className="text-white font-semibold text-base">
|
||||
{formatCurrency(order.totalAmount)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Информация о поставщике */}
|
||||
<div className="mb-3">
|
||||
<h4 className="text-white font-semibold mb-1.5 flex items-center text-sm">
|
||||
<Building className="h-4 w-4 mr-1.5 text-blue-400" />
|
||||
Информация о селлере
|
||||
</h4>
|
||||
<div className="bg-white/5 rounded p-2 space-y-1.5">
|
||||
{order.partner.address && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<MapPin className="h-3 w-3 text-white/60 flex-shrink-0" />
|
||||
<span className="text-white/80 text-sm">
|
||||
{order.partner.address}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{order.partner.phones &&
|
||||
order.partner.phones.length > 0 && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Phone className="h-3 w-3 text-white/60 flex-shrink-0" />
|
||||
<span className="text-white/80 text-sm">
|
||||
{order.partner.phones.join(", ")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{order.partner.emails &&
|
||||
order.partner.emails.length > 0 && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Mail className="h-3 w-3 text-white/60 flex-shrink-0" />
|
||||
<span className="text-white/80 text-sm">
|
||||
{order.partner.emails.join(", ")}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Список товаров */}
|
||||
<div>
|
||||
<h4 className="text-white font-semibold mb-1.5 flex items-center text-sm">
|
||||
<Package className="h-4 w-4 mr-1.5 text-green-400" />
|
||||
Товары ({order.items.length})
|
||||
</h4>
|
||||
<div className="space-y-1.5">
|
||||
{order.items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="bg-white/5 rounded p-2 flex items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center space-x-2 flex-1 min-w-0">
|
||||
{item.product.mainImage && (
|
||||
<img
|
||||
src={item.product.mainImage}
|
||||
alt={item.product.name}
|
||||
className="w-8 h-8 rounded object-cover flex-shrink-0"
|
||||
/>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<h5 className="text-white font-medium text-sm truncate">
|
||||
{item.product.name}
|
||||
</h5>
|
||||
<p className="text-white/60 text-xs">
|
||||
{item.product.article}
|
||||
</p>
|
||||
{item.product.category && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="bg-blue-500/20 text-blue-300 text-xs mt-0.5 px-1.5 py-0.5"
|
||||
>
|
||||
{item.product.category.name}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
<div className="text-right flex-shrink-0">
|
||||
<p className="text-white font-semibold text-sm">
|
||||
{item.quantity} шт.
|
||||
</p>
|
||||
<p className="text-white/60 text-xs">
|
||||
{formatCurrency(item.price)}
|
||||
</p>
|
||||
<p className="text-green-400 font-semibold text-sm">
|
||||
{formatCurrency(item.totalPrice)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user