feat: Implement comprehensive three-party supply order workflow system
- Added logistics partner selection as mandatory requirement for fulfillment supply orders - Implemented complete status workflow: PENDING → SUPPLIER_APPROVED → LOGISTICS_CONFIRMED → SHIPPED → DELIVERED - Created dedicated interfaces for all three parties: * Fulfillment: Create orders with mandatory logistics selection and receive shipments * Suppliers: View, approve/reject orders, and ship approved orders via /supplies tab * Logistics: Confirm/reject transport requests via new /logistics-orders dashboard - Updated Prisma schema with logisticsPartnerId (non-nullable) and new SupplyOrderStatus enum - Added comprehensive GraphQL mutations for each party's workflow actions - Fixed GraphQL resolver to include logistics partners in supplyOrders query - Enhanced UI components with proper status badges and action buttons - Added backward compatibility for legacy status handling - Updated sidebar navigation routing for LOGIST organization type 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
592
src/components/logistics-orders/logistics-orders-dashboard.tsx
Normal file
592
src/components/logistics-orders/logistics-orders-dashboard.tsx
Normal file
@ -0,0 +1,592 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { Sidebar } from "@/components/dashboard/sidebar";
|
||||
import { useSidebar } from "@/hooks/useSidebar";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { GET_SUPPLY_ORDERS } from "@/graphql/queries";
|
||||
import {
|
||||
LOGISTICS_CONFIRM_ORDER,
|
||||
LOGISTICS_REJECT_ORDER
|
||||
} from "@/graphql/mutations";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
Calendar,
|
||||
Package,
|
||||
Truck,
|
||||
User,
|
||||
CheckCircle,
|
||||
Clock,
|
||||
XCircle,
|
||||
MapPin,
|
||||
Phone,
|
||||
Mail,
|
||||
Building,
|
||||
Hash,
|
||||
AlertTriangle,
|
||||
} from "lucide-react";
|
||||
|
||||
interface SupplyOrder {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
partnerId: string;
|
||||
deliveryDate: string;
|
||||
status: "PENDING" | "SUPPLIER_APPROVED" | "LOGISTICS_CONFIRMED" | "SHIPPED" | "DELIVERED" | "CANCELLED";
|
||||
totalAmount: number;
|
||||
totalItems: number;
|
||||
createdAt: string;
|
||||
organization: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
type: string;
|
||||
};
|
||||
partner: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
type: string;
|
||||
};
|
||||
logisticsPartner?: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
type: string;
|
||||
};
|
||||
items: Array<{
|
||||
id: string;
|
||||
quantity: number;
|
||||
price: number;
|
||||
totalPrice: number;
|
||||
product: {
|
||||
id: string;
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
category?: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export function LogisticsOrdersDashboard() {
|
||||
const { getSidebarMargin } = useSidebar();
|
||||
const { user } = useAuth();
|
||||
const [expandedOrders, setExpandedOrders] = useState<Set<string>>(new Set());
|
||||
const [rejectReason, setRejectReason] = useState<string>("");
|
||||
const [showRejectModal, setShowRejectModal] = useState<string | null>(null);
|
||||
|
||||
// Загружаем заказы поставок
|
||||
const { data, loading, error, refetch } = useQuery(GET_SUPPLY_ORDERS, {
|
||||
fetchPolicy: "cache-and-network",
|
||||
});
|
||||
|
||||
console.log(`DEBUG ЛОГИСТИКА: loading=${loading}, error=${error?.message}, totalOrders=${data?.supplyOrders?.length || 0}`);
|
||||
|
||||
// Мутации для действий логистики
|
||||
const [logisticsConfirmOrder] = useMutation(LOGISTICS_CONFIRM_ORDER, {
|
||||
refetchQueries: [{ query: GET_SUPPLY_ORDERS }],
|
||||
onCompleted: (data) => {
|
||||
if (data.logisticsConfirmOrder.success) {
|
||||
toast.success(data.logisticsConfirmOrder.message);
|
||||
} else {
|
||||
toast.error(data.logisticsConfirmOrder.message);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error confirming order:", error);
|
||||
toast.error("Ошибка при подтверждении заказа");
|
||||
},
|
||||
});
|
||||
|
||||
const [logisticsRejectOrder] = useMutation(LOGISTICS_REJECT_ORDER, {
|
||||
refetchQueries: [{ query: GET_SUPPLY_ORDERS }],
|
||||
onCompleted: (data) => {
|
||||
if (data.logisticsRejectOrder.success) {
|
||||
toast.success(data.logisticsRejectOrder.message);
|
||||
} else {
|
||||
toast.error(data.logisticsRejectOrder.message);
|
||||
}
|
||||
setShowRejectModal(null);
|
||||
setRejectReason("");
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Error rejecting order:", error);
|
||||
toast.error("Ошибка при отклонении заказа");
|
||||
},
|
||||
});
|
||||
|
||||
const toggleOrderExpansion = (orderId: string) => {
|
||||
const newExpanded = new Set(expandedOrders);
|
||||
if (newExpanded.has(orderId)) {
|
||||
newExpanded.delete(orderId);
|
||||
} else {
|
||||
newExpanded.add(orderId);
|
||||
}
|
||||
setExpandedOrders(newExpanded);
|
||||
};
|
||||
|
||||
// Фильтруем заказы где текущая организация является логистическим партнером
|
||||
const logisticsOrders: SupplyOrder[] = (data?.supplyOrders || []).filter(
|
||||
(order: SupplyOrder) => {
|
||||
const isLogisticsPartner = order.logisticsPartner?.id === user?.organization?.id;
|
||||
console.log(`DEBUG ЛОГИСТИКА: Заказ ${order.id.slice(-8)} - статус: ${order.status}, logisticsPartnerId: ${order.logisticsPartner?.id}, currentOrgId: ${user?.organization?.id}, isLogisticsPartner: ${isLogisticsPartner}`);
|
||||
return isLogisticsPartner;
|
||||
}
|
||||
);
|
||||
|
||||
const getStatusBadge = (status: SupplyOrder["status"]) => {
|
||||
const statusMap = {
|
||||
PENDING: {
|
||||
label: "Ожидает поставщика",
|
||||
color: "bg-gray-500/20 text-gray-300 border-gray-500/30",
|
||||
icon: Clock,
|
||||
},
|
||||
SUPPLIER_APPROVED: {
|
||||
label: "Требует подтверждения",
|
||||
color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30",
|
||||
icon: AlertTriangle,
|
||||
},
|
||||
LOGISTICS_CONFIRMED: {
|
||||
label: "Подтверждено",
|
||||
color: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
SHIPPED: {
|
||||
label: "В пути",
|
||||
color: "bg-orange-500/20 text-orange-300 border-orange-500/30",
|
||||
icon: Truck,
|
||||
},
|
||||
DELIVERED: {
|
||||
label: "Доставлено",
|
||||
color: "bg-green-500/20 text-green-300 border-green-500/30",
|
||||
icon: Package,
|
||||
},
|
||||
CANCELLED: {
|
||||
label: "Отменено",
|
||||
color: "bg-red-500/20 text-red-300 border-red-500/30",
|
||||
icon: XCircle,
|
||||
},
|
||||
// Устаревшие статусы для обратной совместимости
|
||||
CONFIRMED: {
|
||||
label: "Подтверждён (устаревший)",
|
||||
color: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
IN_TRANSIT: {
|
||||
label: "В пути (устаревший)",
|
||||
color: "bg-orange-500/20 text-orange-300 border-orange-500/30",
|
||||
icon: Truck,
|
||||
},
|
||||
};
|
||||
|
||||
const config = statusMap[status as keyof typeof statusMap];
|
||||
if (!config) {
|
||||
console.warn(`Unknown status: ${status}`);
|
||||
// Fallback для неизвестных статусов
|
||||
return (
|
||||
<Badge className="bg-gray-500/20 text-gray-300 border-gray-500/30 border flex items-center gap-1 text-xs">
|
||||
<AlertTriangle className="h-3 w-3" />
|
||||
{status}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
const { label, color, icon: Icon } = config;
|
||||
return (
|
||||
<Badge className={`${color} border flex items-center gap-1 text-xs`}>
|
||||
<Icon className="h-3 w-3" />
|
||||
{label}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const handleConfirmOrder = async (orderId: string) => {
|
||||
await logisticsConfirmOrder({ variables: { id: orderId } });
|
||||
};
|
||||
|
||||
const handleRejectOrder = async (orderId: string) => {
|
||||
await logisticsRejectOrder({
|
||||
variables: { id: orderId, reason: rejectReason || undefined },
|
||||
});
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString("ru-RU", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
currency: "RUB",
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
const getInitials = (name: string): string => {
|
||||
return name
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0))
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 flex flex-col transition-all duration-300 overflow-hidden`}>
|
||||
<div className="flex-1 overflow-y-auto flex items-center justify-center">
|
||||
<div className="text-white">Загрузка заказов...</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 flex flex-col transition-all duration-300 overflow-hidden`}>
|
||||
<div className="flex-1 overflow-y-auto flex items-center justify-center">
|
||||
<div className="text-red-300">Ошибка загрузки заказов: {error.message}</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 flex flex-col transition-all duration-300 overflow-hidden`}>
|
||||
<div className="flex-1 overflow-y-auto space-y-6">
|
||||
{/* Заголовок */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white mb-2">
|
||||
Логистические заказы
|
||||
</h1>
|
||||
<p className="text-white/60">
|
||||
Управление заказами поставок и логистическими операциями
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Статистика */}
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-yellow-500/20 rounded">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-sm">Требуют подтверждения</p>
|
||||
<p className="text-xl font-bold text-white">
|
||||
{logisticsOrders.filter(order => order.status === "SUPPLIER_APPROVED").length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-blue-500/20 rounded">
|
||||
<CheckCircle className="h-5 w-5 text-blue-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-sm">Подтверждено</p>
|
||||
<p className="text-xl font-bold text-white">
|
||||
{logisticsOrders.filter(order => order.status === "LOGISTICS_CONFIRMED").length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-orange-500/20 rounded">
|
||||
<Truck className="h-5 w-5 text-orange-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-sm">В пути</p>
|
||||
<p className="text-xl font-bold text-white">
|
||||
{logisticsOrders.filter(order => order.status === "SHIPPED").length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="p-2 bg-green-500/20 rounded">
|
||||
<Package className="h-5 w-5 text-green-400" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-sm">Доставлено</p>
|
||||
<p className="text-xl font-bold text-white">
|
||||
{logisticsOrders.filter(order => order.status === "DELIVERED").length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Список заказов */}
|
||||
<div className="space-y-4">
|
||||
{logisticsOrders.length === 0 ? (
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-8">
|
||||
<div className="text-center">
|
||||
<Truck className="h-12 w-12 text-white/40 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-white mb-2">
|
||||
Нет логистических заказов
|
||||
</h3>
|
||||
<p className="text-white/60">
|
||||
Заказы поставок, требующие логистического сопровождения, будут отображаться здесь
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
logisticsOrders.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="p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
{/* Левая часть */}
|
||||
<div className="flex items-center space-x-4 flex-1 min-w-0">
|
||||
{/* Номер заказа */}
|
||||
<div className="flex items-center space-x-2">
|
||||
<Hash className="h-4 w-4 text-white/60" />
|
||||
<span className="text-white font-semibold">
|
||||
{order.id.slice(-8)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Маршрут */}
|
||||
<div className="flex items-center space-x-3 min-w-0">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Avatar className="w-8 h-8">
|
||||
<AvatarFallback className="bg-blue-500 text-white text-sm">
|
||||
{getInitials(order.partner.name || order.partner.fullName || "П")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-white/60 text-sm">→</span>
|
||||
<Avatar className="w-8 h-8">
|
||||
<AvatarFallback className="bg-green-500 text-white text-sm">
|
||||
{getInitials(order.organization.name || order.organization.fullName || "ФФ")}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h3 className="text-white font-medium text-sm truncate">
|
||||
{order.partner.name || order.partner.fullName} → {order.organization.name || order.organization.fullName}
|
||||
</h3>
|
||||
<p className="text-white/60 text-xs">
|
||||
Поставщик → Фулфилмент
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Краткая информация */}
|
||||
<div className="hidden lg:flex items-center space-x-4">
|
||||
<div className="flex items-center space-x-1">
|
||||
<Calendar className="h-4 w-4 text-blue-400" />
|
||||
<span className="text-white text-sm">
|
||||
{formatDate(order.deliveryDate)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center space-x-1">
|
||||
<Package className="h-4 w-4 text-green-400" />
|
||||
<span className="text-white text-sm">
|
||||
{order.totalItems} шт.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Правая часть - статус и действия */}
|
||||
<div className="flex items-center space-x-3 flex-shrink-0">
|
||||
{getStatusBadge(order.status)}
|
||||
|
||||
{/* Кнопки действий для логистики */}
|
||||
{order.status === "SUPPLIER_APPROVED" && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleConfirmOrder(order.id);
|
||||
}}
|
||||
className="bg-green-500/20 hover:bg-green-500/30 text-green-300 border border-green-500/30 text-xs px-3 py-1 h-7"
|
||||
>
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Подтвердить
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowRejectModal(order.id);
|
||||
}}
|
||||
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 border border-red-500/30 text-xs px-3 py-1 h-7"
|
||||
>
|
||||
<XCircle className="h-3 w-3 mr-1" />
|
||||
Отклонить
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Развернутые детали */}
|
||||
{expandedOrders.has(order.id) && (
|
||||
<>
|
||||
<Separator className="my-4 bg-white/10" />
|
||||
|
||||
{/* Сумма заказа */}
|
||||
<div className="mb-4 p-3 bg-white/5 rounded">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-white/60">Общая сумма:</span>
|
||||
<span className="text-white font-semibold text-lg">
|
||||
{formatCurrency(order.totalAmount)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Информация о маршруте */}
|
||||
<div className="mb-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<h4 className="text-white font-semibold mb-2 flex items-center text-sm">
|
||||
<Building className="h-4 w-4 mr-2 text-blue-400" />
|
||||
Поставщик
|
||||
</h4>
|
||||
<div className="bg-white/5 rounded p-3">
|
||||
<p className="text-white">
|
||||
{order.partner.name || order.partner.fullName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="text-white font-semibold mb-2 flex items-center text-sm">
|
||||
<Package className="h-4 w-4 mr-2 text-green-400" />
|
||||
Получатель
|
||||
</h4>
|
||||
<div className="bg-white/5 rounded p-3">
|
||||
<p className="text-white">
|
||||
{order.organization.name || order.organization.fullName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Список товаров */}
|
||||
<div>
|
||||
<h4 className="text-white font-semibold mb-3 flex items-center text-sm">
|
||||
<Package className="h-4 w-4 mr-2 text-green-400" />
|
||||
Товары к доставке ({order.items.length})
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{order.items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="bg-white/5 rounded p-3 flex items-center justify-between"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h5 className="text-white font-medium text-sm">
|
||||
{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-1"
|
||||
>
|
||||
{item.product.category.name}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-right flex-shrink-0 ml-4">
|
||||
<p className="text-white font-semibold">
|
||||
{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>
|
||||
|
||||
{/* Модальное окно для отклонения заказа */}
|
||||
{showRejectModal && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<Card className="bg-gray-900 border-white/20 p-6 max-w-md w-full mx-4">
|
||||
<h3 className="text-white font-semibold text-lg mb-4">
|
||||
Отклонить логистический заказ
|
||||
</h3>
|
||||
<p className="text-white/60 text-sm mb-4">
|
||||
Укажите причину отклонения заказа (необязательно):
|
||||
</p>
|
||||
<textarea
|
||||
value={rejectReason}
|
||||
onChange={(e) => setRejectReason(e.target.value)}
|
||||
placeholder="Причина отклонения..."
|
||||
className="w-full bg-white/10 border border-white/20 rounded-md px-3 py-2 text-white text-sm focus:outline-none focus:ring-1 focus:ring-red-500 focus:border-transparent mb-4"
|
||||
rows={3}
|
||||
/>
|
||||
<div className="flex items-center space-x-3">
|
||||
<Button
|
||||
onClick={() => handleRejectOrder(showRejectModal)}
|
||||
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 border border-red-500/30"
|
||||
>
|
||||
Отклонить заказ
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setShowRejectModal(null);
|
||||
setRejectReason("");
|
||||
}}
|
||||
variant="outline"
|
||||
className="border-white/20 text-white hover:bg-white/10"
|
||||
>
|
||||
Отмена
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user