752 lines
36 KiB
TypeScript
752 lines
36 KiB
TypeScript
"use client";
|
||
import React, { useState } from "react";
|
||
import Link from "next/link";
|
||
import { motion, AnimatePresence } from "motion/react";
|
||
import {
|
||
X,
|
||
Download,
|
||
Package,
|
||
ShoppingBag,
|
||
MapPin,
|
||
Clock,
|
||
CreditCard,
|
||
CheckCircle2,
|
||
Truck,
|
||
PackageCheck,
|
||
XCircle,
|
||
AlertCircle,
|
||
Wallet,
|
||
Building2,
|
||
Loader2,
|
||
ArrowRight,
|
||
Sparkles,
|
||
} from "lucide-react";
|
||
import { toPersian } from "../lib/utils";
|
||
import api from "../lib/services/api";
|
||
import { toast } from "sonner";
|
||
import { useSettingsStore } from "../lib/store/settingsStore";
|
||
import SafeImage from "./SafeImage";
|
||
|
||
interface OrderDetailsModalProps {
|
||
isOpen: boolean;
|
||
onClose: () => void;
|
||
order: Record<string, unknown> | null;
|
||
onOrderUpdated?: (orderId: string, newStatus: string) => void;
|
||
}
|
||
|
||
export default function OrderDetailsModal({
|
||
isOpen,
|
||
onClose,
|
||
order,
|
||
onOrderUpdated,
|
||
}: OrderDetailsModalProps) {
|
||
const getText = useSettingsStore((state) => state.getText);
|
||
const isCardToCardEnabled = getText('PAY_GATEWAY_CARD_ENABLE', 'true') === 'true' && getText('cart_to_cart_enabled', 'true') === 'true';
|
||
|
||
const [retryModalOpen, setRetryModalOpen] = useState(false);
|
||
const [selectedMethod, setSelectedMethod] = useState<'online' | 'wallet' | 'card'>('online');
|
||
const [isProcessingPayment, setIsProcessingPayment] = useState(false);
|
||
const [zoomedImage, setZoomedImage] = useState<string | null>(null);
|
||
|
||
if (!order) return null;
|
||
|
||
const orderId = String(order.id || '');
|
||
const orderStatus = String(order.status || '');
|
||
const isPendingPayment = orderStatus === 'pending_payment';
|
||
|
||
const getProductName = (p: Record<string, unknown> | undefined) =>
|
||
String(p?.nameFa || p?.name || p?.nameEn || 'محصول کنینا');
|
||
const getProductImage = (p: Record<string, unknown> | undefined) =>
|
||
String(p?.imageUrl || p?.image || '/products/product-placeholder.png');
|
||
const displayTrackingCode = String(
|
||
order.trackingNumber || (typeof order.id === 'string' ? order.id.substring(0, 8) : '') || 'CN-0000'
|
||
);
|
||
|
||
const shippingAddress = (order.shippingAddress || order.address || {}) as Record<string, unknown>;
|
||
|
||
const rawDate = order.date || order.createdAt;
|
||
const orderDate = rawDate ? new Date(String(rawDate)) : new Date();
|
||
const formattedDate = orderDate.toLocaleDateString("fa-IR");
|
||
const formattedTime = orderDate.toLocaleTimeString("fa-IR", { hour: '2-digit', minute: '2-digit' });
|
||
|
||
const items = Array.isArray(order.items)
|
||
? (order.items as Record<string, unknown>[])
|
||
: Array.isArray(order.orderItems)
|
||
? (order.orderItems as Record<string, unknown>[])
|
||
: [];
|
||
const itemsSubtotal = items.reduce((sum: number, item: Record<string, unknown>) => {
|
||
const prod = item.product as Record<string, unknown> | undefined;
|
||
const price = Number(prod?.priceValue || item.priceValue || 0);
|
||
return sum + price * Number(item.quantity || 1);
|
||
}, 0);
|
||
|
||
const charityAmount = Number(order.charityDonation || 0);
|
||
const totalAmount = Number(order.total || order.totalAmount || itemsSubtotal + charityAmount);
|
||
const isRefill = Boolean(order.isRefill);
|
||
|
||
const getPaymentMethodTitle = (method?: unknown) => {
|
||
const m = String(method || '');
|
||
if (m === 'wallet') return 'کیف پول الکترونیک کنینا';
|
||
if (m === 'card' || m === 'card_to_card') return 'کارت به کارت مستقیم';
|
||
return 'درگاه پرداخت اینترنتی زیبال (شتاب)';
|
||
};
|
||
|
||
const handlePrint = () => {
|
||
const printWindow = window.open('', '_blank');
|
||
if (!printWindow) return;
|
||
|
||
const itemsHtml = items
|
||
.map((item: Record<string, unknown>) => {
|
||
const prod = item.product as Record<string, unknown> | undefined;
|
||
const pName = getProductName(prod);
|
||
const pImg = getProductImage(prod);
|
||
const pPrice = Number(prod?.priceValue || item.priceValue || 0);
|
||
const rowTotal = pPrice * Number(item.quantity || 1);
|
||
return `
|
||
<tr>
|
||
<td style="padding: 14px; border-bottom: 1px solid #e5e7eb; vertical-align: middle;">
|
||
<div style="display: flex; align-items: center; gap: 12px;">
|
||
<img src="${pImg}" alt="${pName}" style="width: 48px; height: 48px; object-fit: contain; border-radius: 8px; border: 1px solid #f3f4f6; background: #fff; padding: 4px;" />
|
||
<span style="font-weight: 700; color: #111827;">${pName}</span>
|
||
</div>
|
||
</td>
|
||
<td style="padding: 14px; border-bottom: 1px solid #e5e7eb; text-align: center; font-weight: 700;">${toPersian(
|
||
Number(item.quantity || 1)
|
||
)}</td>
|
||
<td style="padding: 14px; border-bottom: 1px solid #e5e7eb; text-align: left; font-weight: 700;">${toPersian(
|
||
pPrice.toLocaleString()
|
||
)} تومان</td>
|
||
<td style="padding: 14px; border-bottom: 1px solid #e5e7eb; text-align: left; font-weight: 900; color: #0055FF;">${toPersian(
|
||
rowTotal.toLocaleString()
|
||
)} تومان</td>
|
||
</tr>
|
||
`;
|
||
})
|
||
.join('');
|
||
|
||
printWindow.document.write(`
|
||
<!DOCTYPE html>
|
||
<html dir="rtl" lang="fa">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<title>فاکتور رسمی کنینا - ${displayTrackingCode}</title>
|
||
<style>
|
||
@font-face {
|
||
font-family: 'Vazirmatn';
|
||
src: url('/fonts/vazirmatn-v33.003/fonts/webfonts/Vazirmatn-Regular.woff2') format('woff2');
|
||
}
|
||
body { font-family: 'Vazirmatn', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 40px; color: #1f2937; background: #fff; line-height: 1.6; }
|
||
.invoice-card { max-width: 850px; margin: 0 auto; border: 2px solid #e5e7eb; border-radius: 24px; padding: 40px; }
|
||
.header { display: flex; justify-content: space-between; align-items: center; border-bottom: 3px solid #0055FF; padding-bottom: 24px; margin-bottom: 32px; }
|
||
.logo-text { font-size: 28px; font-weight: 900; color: #0055FF; font-style: italic; }
|
||
.sub-logo { font-size: 13px; color: #6b7280; font-weight: 700; margin-top: 4px; }
|
||
.meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; background: #f9fafb; padding: 20px; border-radius: 16px; margin-bottom: 32px; font-size: 13px; }
|
||
table { width: 100%; border-collapse: collapse; margin-bottom: 32px; }
|
||
th { background: #f3f4f6; text-align: right; padding: 14px; font-size: 12px; font-weight: 900; color: #4b5563; border-radius: 8px; }
|
||
.summary-box { background: #0f172a; color: #fff; padding: 24px; border-radius: 20px; margin-top: 32px; }
|
||
.summary-row { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 10px; font-weight: 700; color: #94a3b8; }
|
||
.summary-row.total { font-size: 20px; font-weight: 900; color: #fbbf24; border-top: 1px solid #334155; padding-top: 14px; margin-top: 14px; margin-bottom: 0; }
|
||
.footer { margin-top: 40px; text-align: center; font-size: 11px; color: #9ca3af; font-weight: 700; }
|
||
@media print {
|
||
body { padding: 0; background: #fff; }
|
||
.invoice-card { border: none; padding: 0; max-width: 100%; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="invoice-card">
|
||
<div class="header">
|
||
<div>
|
||
<div class="logo-text">Canina Iran</div>
|
||
<div class="sub-logo">نماینده رسمی Canina pharma GmbH آلمان در ایران</div>
|
||
</div>
|
||
<div style="text-align: left;">
|
||
<h2 style="margin: 0; font-size: 22px; font-weight: 900; color: #111827;">صورتحساب فروش کالا و خدمات</h2>
|
||
<span style="font-size: 12px; font-weight: 700; color: #0055FF; margin-top: 4px; display: block;">شناسه فاکتور: ${toPersian(
|
||
displayTrackingCode
|
||
)}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="meta-grid">
|
||
<div>
|
||
<strong style="color: #4b5563;">اطلاعات سفارش:</strong>
|
||
<div style="margin-top: 6px;">تاریخ ثبت: ${toPersian(formattedDate)} - ساعت ${toPersian(
|
||
formattedTime
|
||
)}</div>
|
||
<div style="margin-top: 4px;">روش پرداخت: ${getPaymentMethodTitle(order.paymentMethod)}</div>
|
||
<div style="margin-top: 4px;">وضعیت: ${getStatusLabel(orderStatus)}</div>
|
||
</div>
|
||
<div>
|
||
<strong style="color: #4b5563;">نشانی تحویل گیرنده:</strong>
|
||
<div style="margin-top: 6px; line-height: 1.5;">${
|
||
String(order.shippingAddress || 'آدرس ثبتی کاربر در حساب کاربری')
|
||
}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>شرح کالا / مکمل دارویی</th>
|
||
<th style="text-align: center;">تعداد</th>
|
||
<th style="text-align: left;">مبلغ واحد</th>
|
||
<th style="text-align: left;">مبلغ کل</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${itemsHtml}
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class="summary-box">
|
||
<div class="summary-row">
|
||
<span>جمع اقلام:</span>
|
||
<span>${toPersian(itemsSubtotal.toLocaleString())} تومان</span>
|
||
</div>
|
||
${
|
||
charityAmount > 0
|
||
? `
|
||
<div class="summary-row" style="color: #f472b6;">
|
||
<span>کمک به حیوانات بیسرپرست (ردپای مهربانی):</span>
|
||
<span>${toPersian(charityAmount.toLocaleString())}+ تومان</span>
|
||
</div>
|
||
`
|
||
: ''
|
||
}
|
||
<div class="summary-row">
|
||
<span>هزینه بستهبندی و ارسال اکسپرس:</span>
|
||
<span style="color: #34d399;">رایگان (تحویل سراسری)</span>
|
||
</div>
|
||
<div class="summary-row total">
|
||
<span>مبلغ نهایی فاکتور:</span>
|
||
<span>${toPersian(totalAmount.toLocaleString())} تومان</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
داروخانه تخصصی مکملهای کنینا ایران • تلفن پشتیبانی: ۰۲۱-۸۸۸۸۸۸۸۸ • وبسایت: www.canina.ir
|
||
</div>
|
||
</div>
|
||
<script>
|
||
window.onload = function() {
|
||
setTimeout(function() { window.print(); }, 300);
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|
||
`);
|
||
printWindow.document.close();
|
||
};
|
||
|
||
const handleExecuteRetryPayment = async () => {
|
||
try {
|
||
setIsProcessingPayment(true);
|
||
const res = await api.post(`/orders/${orderId}/retry-payment`, {
|
||
paymentMethod: selectedMethod,
|
||
});
|
||
|
||
if (res.data?.success) {
|
||
if (selectedMethod === 'online') {
|
||
// Initiate Zibal Payment
|
||
const payRes = await api.post('/payment/zibal/initiate', {
|
||
orderId,
|
||
});
|
||
if (payRes.data?.paymentUrl) {
|
||
toast.success('در حال انتقال به درگاه پرداخت زیبال...');
|
||
window.location.href = payRes.data.paymentUrl;
|
||
return;
|
||
}
|
||
} else if (selectedMethod === 'wallet') {
|
||
toast.success('پرداخت سفارش با موفقیت از طریق کیف پول انجام شد.');
|
||
if (onOrderUpdated) {
|
||
onOrderUpdated(orderId, 'processing');
|
||
}
|
||
setRetryModalOpen(false);
|
||
onClose();
|
||
} else {
|
||
toast.success('روش پرداخت به کارت به کارت تغییر یافت.');
|
||
if (onOrderUpdated) {
|
||
onOrderUpdated(orderId, 'pending_payment');
|
||
}
|
||
setRetryModalOpen(false);
|
||
}
|
||
}
|
||
} catch (err: any) {
|
||
console.error('Retry payment error:', err);
|
||
} finally {
|
||
setIsProcessingPayment(false);
|
||
}
|
||
};
|
||
|
||
function getStatusIcon(st: string) {
|
||
switch (st) {
|
||
case 'processing':
|
||
return <Clock className="w-5 h-5 text-amber-500" />;
|
||
case 'packaged':
|
||
case 'ready_to_ship':
|
||
return <PackageCheck className="w-5 h-5 text-purple-500" />;
|
||
case 'shipped':
|
||
return <Truck className="w-5 h-5 text-blue-500" />;
|
||
case 'delivered':
|
||
return <CheckCircle2 className="w-5 h-5 text-green-500" />;
|
||
case 'pending_payment':
|
||
return <AlertCircle className="w-5 h-5 text-rose-500" />;
|
||
case 'cancelled':
|
||
return <XCircle className="w-5 h-5 text-red-500" />;
|
||
default:
|
||
return <Package className="w-5 h-5 text-medical-gray-400" />;
|
||
}
|
||
}
|
||
|
||
function getStatusLabel(st: string) {
|
||
switch (st) {
|
||
case 'processing':
|
||
return 'در حال پردازش در انبار';
|
||
case 'packaged':
|
||
case 'ready_to_ship':
|
||
return 'بستهبندی شده (آماده ارسال)';
|
||
case 'shipped':
|
||
return 'تحویل شرکت پست / پیک';
|
||
case 'delivered':
|
||
return 'تحویل موفق به مشتری';
|
||
case 'pending_payment':
|
||
return 'در انتظار پرداخت / ناموفق';
|
||
case 'cancelled':
|
||
return 'سفارش لغو شده';
|
||
default:
|
||
return 'ثبت اولیه';
|
||
}
|
||
}
|
||
|
||
return (
|
||
<AnimatePresence>
|
||
{isOpen && (
|
||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
exit={{ opacity: 0 }}
|
||
onClick={onClose}
|
||
className="absolute inset-0 bg-medical-gray-900/60 backdrop-blur-md"
|
||
/>
|
||
|
||
<motion.div
|
||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||
exit={{ opacity: 0, scale: 0.95, y: 20 }}
|
||
className="bg-white w-full max-w-3xl rounded-[2.5rem] relative z-10 shadow-2xl overflow-hidden font-vazir text-right"
|
||
dir="rtl"
|
||
>
|
||
{/* Modal Header */}
|
||
<div className="p-6 sm:p-8 bg-medical-gray-50/80 border-b border-medical-gray-100 flex items-center justify-between">
|
||
<div className="flex items-center gap-4">
|
||
<div className="w-12 h-12 bg-canina-blue/10 rounded-2xl flex items-center justify-center text-canina-blue">
|
||
<Package className="w-6 h-6" />
|
||
</div>
|
||
<div>
|
||
<h3 className="text-xl font-black text-medical-gray-900">
|
||
جزئیات کامل سفارش {toPersian(displayTrackingCode)}
|
||
</h3>
|
||
<p className="text-xs text-medical-gray-400 font-bold mt-0.5">
|
||
ثبت شده در تاریخ {toPersian(formattedDate)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={onClose}
|
||
className="w-10 h-10 rounded-xl bg-white border border-medical-gray-200 text-medical-gray-400 hover:text-medical-gray-700 flex items-center justify-center transition-colors"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
{/* Pending Payment Notice Banner */}
|
||
{isPendingPayment && (
|
||
<div className="bg-rose-50 border-b border-rose-200 px-6 py-4 flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||
<div className="flex items-center gap-3">
|
||
<AlertCircle className="w-5 h-5 text-rose-600 shrink-0" />
|
||
<div>
|
||
<span className="text-xs font-black text-rose-900 block">
|
||
پرداخت این سفارش هنوز تکمیل نشده است.
|
||
</span>
|
||
<span className="text-[11px] text-rose-700 font-medium">
|
||
میتوانید همین حالا روش پرداخت را تغییر داده یا مجدداً پرداخت را انجام دهید.
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => setRetryModalOpen(true)}
|
||
className="px-4 py-2 bg-rose-600 hover:bg-rose-700 text-white rounded-xl text-xs font-black transition-all shadow-md shadow-rose-600/20 whitespace-nowrap shrink-0 flex items-center justify-center gap-1.5"
|
||
>
|
||
<CreditCard className="w-4 h-4" />
|
||
<span>پرداخت و تکمیل سفارش</span>
|
||
</button>
|
||
</div>
|
||
)}
|
||
|
||
{/* Content Scrollable */}
|
||
<div className="max-h-[65vh] overflow-y-auto px-6 sm:px-10 py-6 space-y-6 custom-scrollbar">
|
||
{/* Summary Stats */}
|
||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 sm:gap-4">
|
||
<div className="bg-medical-gray-50 p-4 rounded-2xl border border-medical-gray-100">
|
||
<div className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-1.5">
|
||
وضعیت سفارش
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
{getStatusIcon(orderStatus)}
|
||
<span className="text-xs sm:text-sm font-black text-medical-gray-900">
|
||
{getStatusLabel(orderStatus)}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-medical-gray-50 p-4 rounded-2xl border border-medical-gray-100">
|
||
<div className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-1.5">
|
||
کد رهگیری سفارش
|
||
</div>
|
||
<div className="text-xs sm:text-sm font-mono font-black text-canina-blue">
|
||
{toPersian(displayTrackingCode)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-medical-gray-50 p-4 rounded-2xl border border-medical-gray-100">
|
||
<div className="text-[10px] font-black text-medical-gray-400 uppercase tracking-widest mb-1.5">
|
||
زمان ثبت سفارش
|
||
</div>
|
||
<div className="flex items-center gap-1.5 text-xs sm:text-sm font-black text-medical-gray-900">
|
||
<Clock className="w-4 h-4 text-medical-gray-400" />
|
||
<span>{toPersian(formattedTime)} - {toPersian(formattedDate)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Items Section */}
|
||
<div>
|
||
<h4 className="text-sm font-black text-medical-gray-900 mb-3 flex items-center gap-2">
|
||
<ShoppingBag className="w-4 h-4 text-canina-blue" />
|
||
سبد اقلام سفارش
|
||
</h4>
|
||
<div className="space-y-3">
|
||
{items.map((item: Record<string, unknown>, idx: number) => {
|
||
const prod = item.product as Record<string, unknown> | undefined;
|
||
const pName = getProductName(prod);
|
||
const pImg = getProductImage(prod);
|
||
const pPrice = Number(prod?.priceValue || item.priceValue || 0);
|
||
const itemQty = Number(item.quantity || 1);
|
||
const pSlug = String(prod?.slug || prod?.id || '');
|
||
return (
|
||
<div
|
||
key={String(prod?.id || idx)}
|
||
className="flex items-center justify-between p-4 border border-medical-gray-100 rounded-2xl hover:border-canina-blue transition-all"
|
||
>
|
||
<div className="flex items-center gap-4">
|
||
<div
|
||
onClick={() => setZoomedImage(pImg)}
|
||
className="w-14 h-14 bg-medical-gray-50 rounded-xl p-1 flex items-center justify-center shrink-0 border border-medical-gray-200 cursor-zoom-in hover:scale-105 transition-transform overflow-hidden"
|
||
title="مشاهده تصویر بزرگ"
|
||
>
|
||
<SafeImage
|
||
src={pImg}
|
||
alt={pName}
|
||
className="w-full h-full"
|
||
imgClassName="w-full h-full object-contain"
|
||
/>
|
||
</div>
|
||
<div>
|
||
{pSlug ? (
|
||
<Link
|
||
href={`/shop/${pSlug}`}
|
||
onClick={onClose}
|
||
className="font-black text-medical-gray-900 hover:text-canina-blue transition-colors text-xs sm:text-sm inline-block font-vazir"
|
||
>
|
||
{pName}
|
||
</Link>
|
||
) : (
|
||
<h5 className="font-black text-medical-gray-900 text-xs sm:text-sm font-vazir">
|
||
{pName}
|
||
</h5>
|
||
)}
|
||
<p className="text-xs text-medical-gray-400 mt-0.5 font-vazir">
|
||
{toPersian(itemQty)} عدد × {toPersian(pPrice.toLocaleString())} تومان
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div className="text-left font-black text-canina-blue text-xs sm:text-sm font-vazir">
|
||
{toPersian((pPrice * itemQty).toLocaleString())} تومان
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Shipping & Payment */}
|
||
<div className="grid md:grid-cols-2 gap-6 pt-4 border-t border-medical-gray-100">
|
||
<div>
|
||
<h4 className="text-sm font-black text-medical-gray-900 mb-3 flex items-center gap-2">
|
||
<MapPin className="w-4 h-4 text-canina-blue" />
|
||
اطلاعات تحویل و آدرس
|
||
</h4>
|
||
<div className="bg-medical-gray-50 p-4 rounded-2xl space-y-2 text-xs font-bold text-medical-gray-700">
|
||
<div>
|
||
<span className="text-medical-gray-400">گیرنده: </span>
|
||
{String(shippingAddress.recipientName || shippingAddress.name || 'کاربر')}
|
||
</div>
|
||
<div>
|
||
<span className="text-medical-gray-400">شماره تماس: </span>
|
||
<span className="font-mono">{toPersian(String(shippingAddress.phone || shippingAddress.mobile || '-'))}</span>
|
||
</div>
|
||
<div>
|
||
<span className="text-medical-gray-400">نشانی کامل: </span>
|
||
{String(shippingAddress.fullAddress || shippingAddress.address || '-')}
|
||
</div>
|
||
{Boolean(shippingAddress.postalCode) && (
|
||
<div>
|
||
<span className="text-medical-gray-400">کد پستی: </span>
|
||
<span className="font-mono">{toPersian(String(shippingAddress.postalCode))}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h4 className="text-sm font-black text-medical-gray-900 mb-3 flex items-center gap-2">
|
||
<CreditCard className="w-4 h-4 text-canina-blue" />
|
||
اطلاعات و وضعیت پرداخت
|
||
</h4>
|
||
<div className="bg-medical-gray-50 p-4 rounded-2xl space-y-2.5 text-xs font-bold text-medical-gray-700">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-medical-gray-400">شیوه پرداخت:</span>
|
||
<span className="text-medical-gray-900">{getPaymentMethodTitle(order.paymentMethod)}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-medical-gray-400">مجموع اقلام:</span>
|
||
<span>{toPersian(itemsSubtotal.toLocaleString())} تومان</span>
|
||
</div>
|
||
{charityAmount > 0 && (
|
||
<div className="flex justify-between items-center text-pink-500">
|
||
<span>ردپای مهربانی:</span>
|
||
<span>{toPersian(charityAmount.toLocaleString())}+ تومان</span>
|
||
</div>
|
||
)}
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-medical-gray-400">هزینه ارسال:</span>
|
||
<span className="text-green-600">رایگان</span>
|
||
</div>
|
||
<div className="pt-2 border-t border-medical-gray-200/60 flex justify-between items-center text-sm font-black text-medical-gray-900">
|
||
<span>مبلغ نهایی فاکتور:</span>
|
||
<span className="text-canina-blue">{toPersian(totalAmount.toLocaleString())} تومان</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Footer Actions */}
|
||
<div className="p-6 sm:px-10 bg-medical-gray-50 border-t border-medical-gray-100 flex flex-col sm:flex-row items-center justify-between gap-4">
|
||
<button
|
||
onClick={handlePrint}
|
||
className="w-full sm:w-auto px-5 py-2.5 bg-white border border-medical-gray-200 text-medical-gray-700 rounded-xl text-xs font-black hover:border-canina-blue hover:text-canina-blue transition-all flex items-center justify-center gap-2 shadow-xs cursor-pointer"
|
||
>
|
||
<Download className="w-4 h-4" />
|
||
<span>چاپ فاکتور رسمی</span>
|
||
</button>
|
||
|
||
<div className="flex items-center gap-3 w-full sm:w-auto">
|
||
{isPendingPayment && (
|
||
<button
|
||
onClick={() => setRetryModalOpen(true)}
|
||
className="w-full sm:w-auto px-6 py-2.5 bg-green-600 hover:bg-green-700 text-white rounded-xl text-xs font-black shadow-lg shadow-green-600/20 transition-all flex items-center justify-center gap-2 cursor-pointer"
|
||
>
|
||
<CreditCard className="w-4 h-4" />
|
||
<span>پرداخت سفارش</span>
|
||
</button>
|
||
)}
|
||
<button
|
||
onClick={onClose}
|
||
className="w-full sm:w-auto px-6 py-2.5 bg-medical-gray-900 hover:bg-canina-blue text-white rounded-xl text-xs font-black transition-all cursor-pointer flex items-center justify-center gap-1.5"
|
||
>
|
||
<span>بازگشت</span>
|
||
<X className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Retry Payment Modal */}
|
||
{retryModalOpen && (
|
||
<div className="fixed inset-0 z-60 bg-black/60 backdrop-blur-xs flex items-center justify-center p-4">
|
||
<div className="bg-white rounded-3xl p-6 sm:p-8 max-w-md w-full shadow-2xl border border-medical-gray-200 space-y-6">
|
||
<div className="flex items-center justify-between pb-4 border-b border-medical-gray-100">
|
||
<h3 className="text-base font-black text-medical-gray-900">
|
||
انتخاب روش پرداخت سفارش
|
||
</h3>
|
||
<button
|
||
onClick={() => setRetryModalOpen(false)}
|
||
className="text-medical-gray-400 hover:text-medical-gray-600"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
</div>
|
||
|
||
<div className="space-y-3">
|
||
<label
|
||
onClick={() => setSelectedMethod('online')}
|
||
className={`flex items-center justify-between p-4 rounded-2xl border-2 cursor-pointer transition-all ${
|
||
selectedMethod === 'online'
|
||
? 'border-canina-blue bg-canina-blue/5'
|
||
: 'border-medical-gray-100 hover:border-medical-gray-200'
|
||
}`}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-10 h-10 bg-green-50 text-green-600 rounded-xl flex items-center justify-center font-bold">
|
||
<CreditCard className="w-5 h-5" />
|
||
</div>
|
||
<div>
|
||
<div className="text-xs font-black text-medical-gray-900">
|
||
درگاه آنلاین بانکی
|
||
</div>
|
||
<div className="text-[10px] text-medical-gray-400 font-bold mt-0.5">
|
||
زیبال / کلیه کارتهای عضو شتاب
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${
|
||
selectedMethod === 'online'
|
||
? 'border-canina-blue bg-canina-blue'
|
||
: 'border-medical-gray-300'
|
||
}`}
|
||
>
|
||
{selectedMethod === 'online' && (
|
||
<span className="w-2 h-2 rounded-full bg-white" />
|
||
)}
|
||
</div>
|
||
</label>
|
||
|
||
<label
|
||
onClick={() => setSelectedMethod('wallet')}
|
||
className={`flex items-center justify-between p-4 rounded-2xl border-2 cursor-pointer transition-all ${
|
||
selectedMethod === 'wallet'
|
||
? 'border-canina-blue bg-canina-blue/5'
|
||
: 'border-medical-gray-100 hover:border-medical-gray-200'
|
||
}`}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-10 h-10 bg-amber-50 text-amber-600 rounded-xl flex items-center justify-center font-bold">
|
||
<Wallet className="w-5 h-5" />
|
||
</div>
|
||
<div>
|
||
<div className="text-xs font-black text-medical-gray-900">
|
||
کیف پول حساب کاربری
|
||
</div>
|
||
<div className="text-[10px] text-medical-gray-400 font-bold mt-0.5">
|
||
کسر مستقیم از موجودی کیف پول
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${
|
||
selectedMethod === 'wallet'
|
||
? 'border-canina-blue bg-canina-blue'
|
||
: 'border-medical-gray-300'
|
||
}`}
|
||
>
|
||
{selectedMethod === 'wallet' && (
|
||
<span className="w-2 h-2 rounded-full bg-white" />
|
||
)}
|
||
</div>
|
||
</label>
|
||
|
||
{isCardToCardEnabled && (
|
||
<label
|
||
onClick={() => setSelectedMethod('card')}
|
||
className={`flex items-center justify-between p-4 rounded-2xl border-2 cursor-pointer transition-all ${
|
||
selectedMethod === 'card'
|
||
? 'border-canina-blue bg-canina-blue/5'
|
||
: 'border-medical-gray-100 hover:border-medical-gray-200'
|
||
}`}
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-10 h-10 bg-blue-50 text-blue-600 rounded-xl flex items-center justify-center font-bold">
|
||
<Building2 className="w-5 h-5" />
|
||
</div>
|
||
<div>
|
||
<div className="text-xs font-black text-medical-gray-900">
|
||
کارت به کارت مستقیم
|
||
</div>
|
||
<div className="text-[10px] text-medical-gray-400 font-bold mt-0.5">
|
||
واریز به شماره کارت / شبا شرکت
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div
|
||
className={`w-5 h-5 rounded-full border-2 flex items-center justify-center ${
|
||
selectedMethod === 'card'
|
||
? 'border-canina-blue bg-canina-blue'
|
||
: 'border-medical-gray-300'
|
||
}`}
|
||
>
|
||
{selectedMethod === 'card' && (
|
||
<span className="w-2 h-2 rounded-full bg-white" />
|
||
)}
|
||
</div>
|
||
</label>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex items-center justify-end gap-3 pt-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => setRetryModalOpen(false)}
|
||
className="px-4 py-2.5 text-xs font-bold text-medical-gray-600 hover:bg-medical-gray-100 rounded-xl transition-all"
|
||
>
|
||
انصراف
|
||
</button>
|
||
<button
|
||
type="button"
|
||
disabled={isProcessingPayment}
|
||
onClick={handleExecuteRetryPayment}
|
||
className="px-6 py-2.5 text-xs font-black bg-canina-blue hover:bg-canina-dark text-white rounded-xl shadow-lg shadow-canina-blue/20 transition-all flex items-center gap-2"
|
||
>
|
||
{isProcessingPayment ? (
|
||
<Loader2 className="w-4 h-4 animate-spin" />
|
||
) : (
|
||
<CheckCircle2 className="w-4 h-4" />
|
||
)}
|
||
<span>تایید و ادامه پرداخت</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Image Zoom Modal */}
|
||
{zoomedImage && (
|
||
<div
|
||
className="fixed inset-0 z-70 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4"
|
||
onClick={() => setZoomedImage(null)}
|
||
>
|
||
<div className="relative max-w-lg w-full bg-white p-6 rounded-3xl shadow-2xl flex flex-col items-center">
|
||
<button
|
||
onClick={() => setZoomedImage(null)}
|
||
className="absolute top-4 left-4 p-2 rounded-full bg-medical-gray-100 hover:bg-medical-gray-200 text-medical-gray-700 transition-colors cursor-pointer"
|
||
>
|
||
<X className="w-5 h-5" />
|
||
</button>
|
||
<SafeImage
|
||
src={zoomedImage}
|
||
alt="نمایش کامل تصویر محصول"
|
||
className="max-h-[70vh] w-full"
|
||
imgClassName="max-h-[70vh] w-auto object-contain rounded-2xl mx-auto"
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</motion.div>
|
||
</div>
|
||
)}
|
||
</AnimatePresence>
|
||
);
|
||
}
|