canina/frontend/admin-panel/src/pages/Orders.tsx
2026-08-16 18:01:43 +03:30

744 lines
38 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect, useCallback } from 'react';
import {
ShoppingCart,
Eye,
Search,
X,
Printer,
CheckCircle2,
Clock,
Truck,
XCircle,
MapPin,
User,
Phone,
Mail,
Package,
Save,
Download
} from 'lucide-react';
import api from '../services/api';
import Skeleton from '../components/ui/Skeleton';
import Spinner from '../components/ui/Spinner';
import Pagination from '../components/ui/Pagination';
export interface OrderItem {
id?: string;
name?: string;
priceValue?: number;
quantity?: number;
product?: {
nameFa?: string;
name?: string;
priceValue?: number;
imageUrl?: string;
image?: string;
};
}
export interface PaymentTx {
id: string;
trackId?: string;
refNumber?: string;
cardNumber?: string;
amount?: number;
status: string;
message?: string;
gateway?: string;
createdAt: string;
}
export interface Order {
id: string;
trackingNumber?: string;
status: string;
createdAt?: string;
date?: string;
charityDonation?: number;
totalAmount?: number;
total?: number;
isRefill?: boolean;
paymentMethod?: string;
address?: string;
shippingAddress?: string;
orderItems?: OrderItem[];
items?: OrderItem[];
paymentTransactions?: PaymentTx[];
user?: {
firstName?: string;
lastName?: string;
mobile?: string;
phone?: string;
email?: string;
};
}
const statusStyles: Record<string, { label: string; color: string; icon: React.ElementType }> = {
pending_payment: { label: 'در انتظار پرداخت / ناموفق', color: 'bg-rose-100 text-rose-800 border-rose-200', icon: Clock },
pending: { label: 'در حال بررسی', color: 'bg-orange-100 text-orange-700 border-orange-200', icon: Clock },
processing: { label: 'در حال پردازش', color: 'bg-amber-100 text-amber-800 border-amber-200', icon: Clock },
shipped: { label: 'ارسال شده', color: 'bg-blue-100 text-blue-700 border-blue-200', icon: Truck },
delivered: { label: 'تحویل داده شده', color: 'bg-green-100 text-green-700 border-green-200', icon: CheckCircle2 },
cancelled: { label: 'لغو شده', color: 'bg-red-100 text-red-700 border-red-200', icon: XCircle },
};
const toPersianDigits = (n: string | number): string => {
if (n === null || n === undefined) return '';
const farsiDigits = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
return n.toString().replace(/\d/g, (x) => farsiDigits[parseInt(x)]);
};
export default function Orders() {
const [orders, setOrders] = useState<Order[]>([]);
const [isLoading, setIsLoading] = useState(true);
// Queries
const [page, setPage] = useState(1);
const [search, setSearch] = useState('');
const [status, setStatus] = useState('');
const [totalPages, setTotalPages] = useState(1);
const [processingId, setProcessingId] = useState<string | null>(null);
// Modal State
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
const [modalTrackingCode, setModalTrackingCode] = useState('');
const [modalStatus, setModalStatus] = useState('');
const [isSavingTracking, setIsSavingTracking] = useState(false);
const fetchOrders = useCallback(async () => {
try {
setIsLoading(true);
const params = new URLSearchParams();
params.append('page', page.toString());
if (search) params.append('search', search);
if (status) params.append('status', status);
const response = await api.get(`/admin/orders?${params.toString()}`);
if (response.data?.success) {
setOrders(response.data.data);
setTotalPages(response.data.meta?.lastPage || 1);
// Update selectedOrder if open
if (selectedOrder) {
const updated = response.data.data.find((o: Order) => o.id === selectedOrder.id);
if (updated) {
setSelectedOrder(updated);
}
}
}
} catch (err) {
console.error('Failed to fetch orders', err);
} finally {
setIsLoading(false);
}
}, [page, search, status, selectedOrder]);
useEffect(() => {
const delayDebounceFn = setTimeout(() => {
fetchOrders();
}, 500);
return () => clearTimeout(delayDebounceFn);
}, [fetchOrders]);
const handleUpdateStatus = async (id: string, newStatus: string, trackingNumber?: string) => {
try {
setProcessingId(id);
await api.put(`/admin/orders/${id}/status`, {
status: newStatus,
trackingNumber: trackingNumber !== undefined ? trackingNumber : undefined
});
await fetchOrders();
} catch (err) {
console.error('Failed to update status', err);
} finally {
setProcessingId(null);
}
};
const openOrderModal = (order: Order) => {
setSelectedOrder(order);
setModalTrackingCode(order.trackingNumber || '');
setModalStatus(order.status || 'processing');
};
const handleSaveModalChanges = async () => {
if (!selectedOrder) return;
try {
setIsSavingTracking(true);
await api.put(`/admin/orders/${selectedOrder.id}/status`, {
status: modalStatus,
trackingNumber: modalTrackingCode
});
await fetchOrders();
} catch (err) {
console.error('Failed to save tracking number', err);
} finally {
setIsSavingTracking(false);
}
};
const handlePrintInvoice = (orderToPrint: Order) => {
if (!orderToPrint) return;
const printWindow = window.open('', '_blank');
if (!printWindow) return;
const orderDate = new Date(orderToPrint.createdAt || orderToPrint.date || Date.now());
const formattedDate = toPersianDigits(orderDate.toLocaleDateString("fa-IR"));
const formattedTime = toPersianDigits(orderDate.toLocaleTimeString("fa-IR", { hour: '2-digit', minute: '2-digit' }));
const trackingCode = orderToPrint.trackingNumber || orderToPrint.id.substring(0, 8);
const items = orderToPrint.orderItems || orderToPrint.items || [];
const itemsSubtotal = items.reduce((sum: number, item: OrderItem) => {
const price = Number(item.product?.priceValue || item.priceValue || 0);
return sum + price * (item.quantity || 1);
}, 0);
const charityAmount = Number(orderToPrint.charityDonation || 0);
const totalAmount = Number(orderToPrint.totalAmount || orderToPrint.total || itemsSubtotal + charityAmount);
const isRefill = Boolean(orderToPrint.isRefill);
const refillDiscount = isRefill ? Math.round(itemsSubtotal * 0.05) : 0;
const customerName = orderToPrint.user ? `${orderToPrint.user.firstName || ''} ${orderToPrint.user.lastName || ''}`.trim() : 'مشتری مهمان';
const paymentMethodText = orderToPrint.paymentMethod === 'wallet' ? 'پرداخت از کیف پول الکترونیک' : 'پرداخت آنلاین از درگاه شتاب';
const itemsHtml = items.map((item: OrderItem) => {
const pName = item.product?.nameFa || item.product?.name || item.name || 'محصول کنینا';
const pImg = item.product?.imageUrl || item.product?.image || '/products/product-placeholder.png';
const pPrice = Number(item.product?.priceValue || item.priceValue || 0);
const rowTotal = pPrice * (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;">${toPersianDigits(item.quantity)}</td>
<td style="padding: 14px; border-bottom: 1px solid #e5e7eb; text-align: left; font-weight: 700;">${toPersianDigits(pPrice.toLocaleString())} تومان</td>
<td style="padding: 14px; border-bottom: 1px solid #e5e7eb; text-align: left; font-weight: 900; color: #0055FF;">${toPersianDigits(rowTotal.toLocaleString())} تومان</td>
</tr>
`;
}).join('');
printWindow.document.write(`
<!DOCTYPE html>
<html dir="rtl" lang="fa">
<head>
<meta charset="utf-8" />
<title>فاکتور رسمی کنینا - ${trackingCode}</title>
<style>
@font-face {
font-family: 'Vazirmatn';
src: url('https://cdn.jsdelivr.net/gh/rastikerdar/vazirmatn@v33.003/fonts/webfonts/Vazirmatn-Regular.woff2') format('woff2');
}
body { font-family: 'Vazirmatn', 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-between: 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; }
.invoice-card { border: none; padding: 0; }
}
</style>
</head>
<body>
<div class="invoice-card">
<div class="header">
<div>
<div class="logo-text">CANINA | کنینا</div>
<div class="sub-logo">پنل مدیریت سفارشات آلمان</div>
</div>
<div style="text-align: left;">
<div style="font-size: 14px; font-weight: 900; color: #111827;">کد پیگیری: ${toPersianDigits(trackingCode)}</div>
<div style="font-size: 12px; color: #6b7280; font-weight: 700;">تاریخ: ${formattedDate} - ساعت ${formattedTime}</div>
</div>
</div>
<div class="meta-grid">
<div>
<p style="margin: 0 0 6px 0;"><strong>نام خریدار:</strong> ${customerName}</p>
<p style="margin: 0 0 6px 0;"><strong>گیرنده و آدرس:</strong> ${orderToPrint.shippingAddress || "ثبت‌شده در حساب کاربری"}</p>
<p style="margin: 0;"><strong>روش پرداخت:</strong> ${paymentMethodText}</p>
</div>
<div style="text-align: left;">
<p style="margin: 0 0 6px 0;"><strong>تلفن خریدار:</strong> ${toPersianDigits(orderToPrint.user?.phone || 'ثبت نشده')}</p>
<p style="margin: 0 0 6px 0;"><strong>وضعیت سفارش:</strong> ${statusStyles[orderToPrint.status]?.label || orderToPrint.status}</p>
<p style="margin: 0;"><strong>نوع سفارش:</strong> ${isRefill ? 'تمدید خودکار (Refill)' : 'عادی'}</p>
</div>
</div>
<table>
<thead>
<tr>
<th>شرح محصول</th>
<th style="text-align: center;">تعداد</th>
<th>قیمت واحد</th>
<th>مجموع</th>
</tr>
</thead>
<tbody>
${itemsHtml}
</tbody>
</table>
<div class="summary-box">
<div class="summary-row">
<span>مجموع اولیه اقلام:</span>
<span>${toPersianDigits(itemsSubtotal.toLocaleString())} تومان</span>
</div>
${isRefill ? `
<div class="summary-row" style="color: #4ade80;">
<span>تخفیف تمدید خودکار (۵٪):</span>
<span>${toPersianDigits(refillDiscount.toLocaleString())}- تومان</span>
</div>
` : ''}
${charityAmount > 0 ? `
<div class="summary-row" style="color: #f472b6;">
<span>کمک اهدایی (ردپای مهربانی):</span>
<span>${toPersianDigits(charityAmount.toLocaleString())}+ تومان</span>
</div>
` : ''}
<div class="summary-row">
<span>هزینه ارسال:</span>
<span style="color: #4ade80;">رایگان</span>
</div>
<div class="summary-row total">
<span>مبلغ نهایی پرداخت شده:</span>
<span>${toPersianDigits(totalAmount.toLocaleString())} تومان</span>
</div>
</div>
<div class="footer">
این فاکتور رسمی به صورت سیستمی صادر شده و فاقد نیاز به مهر و امضای فیزیکی است.<br/>
پشتیبانی کنینا آلمان: ۰۲۱-۸۸۸۸۸۸۸۸ | www.canina.ir
</div>
</div>
<script>
window.onload = function() {
setTimeout(function() {
window.print();
}, 400);
};
</script>
</body>
</html>
`);
printWindow.document.close();
};
return (
<div className="space-y-6 font-vazir text-right" dir="rtl">
{/* Header */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div>
<h2 className="text-2xl font-black text-gray-900 flex items-center gap-2">
<ShoppingCart className="w-6 h-6 text-purple-600" />
مدیریت سفارشات
</h2>
<p className="text-gray-500 font-medium mt-1">مشاهده فاکتورها، سفارشهای در انتظار/ناموفق، تغییر وضعیت و چاپ فاکتور رسمی</p>
</div>
<div className="flex flex-col sm:flex-row items-center gap-3 w-full sm:w-auto">
<select
className="bg-white border border-gray-200 text-gray-700 px-4 py-2.5 rounded-xl flex items-center gap-2 font-bold hover:bg-gray-50 transition-all outline-none w-full sm:w-auto text-sm"
value={status}
onChange={(e) => { setStatus(e.target.value); setPage(1); }}
>
<option value="">همه وضعیتها</option>
<option value="pending_payment">در انتظار پرداخت / ناموفق</option>
<option value="processing">در حال پردازش</option>
<option value="shipped">ارسال شده</option>
<option value="delivered">تحویل داده شده</option>
<option value="cancelled">لغو شده</option>
</select>
<div className="relative w-full sm:w-72">
<Search className="w-5 h-5 text-gray-400 absolute right-3 top-1/2 -translate-y-1/2" />
<input
type="text"
placeholder="جستجو (شماره سفارش، خریدار، تلفن)..."
className="pl-4 pr-10 py-2.5 border border-gray-200 rounded-xl outline-none focus:ring-2 focus:ring-purple-500 w-full font-medium text-sm"
dir="rtl"
value={search}
onChange={(e) => { setSearch(e.target.value); setPage(1); }}
/>
</div>
</div>
</div>
{/* Quick Filter Pills */}
<div className="flex flex-wrap gap-2 pt-1">
{[
{ label: 'همه سفارش‌ها', value: '' },
{ label: 'در انتظار پرداخت / ناموفق', value: 'pending_payment', color: 'text-rose-700 bg-rose-50 border-rose-200' },
{ label: 'در حال پردازش', value: 'processing', color: 'text-amber-700 bg-amber-50 border-amber-200' },
{ label: 'ارسال شده', value: 'shipped', color: 'text-blue-700 bg-blue-50 border-blue-200' },
{ label: 'تحویل شده', value: 'delivered', color: 'text-green-700 bg-green-50 border-green-200' },
{ label: 'لغو شده', value: 'cancelled', color: 'text-red-700 bg-red-50 border-red-200' },
].map((tab) => {
const isSelected = status === tab.value;
return (
<button
key={tab.value}
onClick={() => { setStatus(tab.value); setPage(1); }}
className={`px-4 py-2 rounded-xl font-bold text-xs border transition-all cursor-pointer ${
isSelected
? 'bg-purple-600 text-white border-purple-600 shadow-md shadow-purple-600/20'
: `${tab.color || 'bg-white text-gray-700 border-gray-200'} hover:bg-gray-50`
}`}
>
{tab.label}
</button>
);
})}
</div>
{/* Orders Table */}
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-right">
<thead className="bg-gray-50 text-gray-500 text-xs uppercase tracking-wider border-b border-gray-200">
<tr>
<th className="py-4 px-6 font-bold">شماره / کد پیگیری</th>
<th className="py-4 px-6 font-bold">مشتری</th>
<th className="py-4 px-6 font-bold">مبلغ کل</th>
<th className="py-4 px-6 font-bold">تاریخ و ساعت ثبت</th>
<th className="py-4 px-6 font-bold">اقلام</th>
<th className="py-4 px-6 font-bold">وضعیت سفارش</th>
<th className="py-4 px-6 font-bold text-center">عملیات ادمین</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100 text-sm">
{isLoading ? (
Array.from({ length: 4 }).map((_, i) => (
<tr key={i}>
<td className="py-4 px-6"><Skeleton className="h-6 w-24" /></td>
<td className="py-4 px-6"><Skeleton className="h-6 w-32" /></td>
<td className="py-4 px-6"><Skeleton className="h-6 w-24" /></td>
<td className="py-4 px-6"><Skeleton className="h-6 w-28" /></td>
<td className="py-4 px-6"><Skeleton className="h-6 w-16" /></td>
<td className="py-4 px-6"><Skeleton className="h-6 w-24" /></td>
<td className="py-4 px-6"><Skeleton className="h-8 w-24 mx-auto" /></td>
</tr>
))
) : orders.length === 0 ? (
<tr>
<td colSpan={7} className="py-12 text-center text-gray-400 font-bold">
هیچ سفارشی یافت نشد.
</td>
</tr>
) : (
orders.map((order) => {
const isProcessing = processingId === order.id;
const displayCode = order.trackingNumber || order.id.slice(0, 8);
const orderDate = new Date(order.createdAt || order.date);
const customerName = order.user ? `${order.user.firstName || ''} ${order.user.lastName || ''}`.trim() : 'کاربر مهمان';
const itemsCount = (order.orderItems || order.items || []).length;
const totalAmt = Number(order.totalAmount || order.total || 0);
return (
<tr key={order.id} className="hover:bg-gray-50/50 transition-colors">
<td className="py-4 px-6 font-bold text-purple-600 font-mono" dir="ltr">
<div>{toPersianDigits(displayCode)}</div>
{order.isRefill && (
<span className="inline-block mt-1 text-[9px] font-black px-2 py-0.5 rounded-full bg-green-50 text-green-700 border border-green-200">
Refill 5%
</span>
)}
</td>
<td className="py-4 px-6">
<div className="font-bold text-gray-900">{customerName}</div>
<div className="text-xs text-gray-400 font-medium" dir="ltr">{toPersianDigits(order.user?.phone || '')}</div>
</td>
<td className="py-4 px-6 font-black text-gray-900">
{toPersianDigits(totalAmt.toLocaleString())} <span className="text-xs font-normal">تومان</span>
</td>
<td className="py-4 px-6 text-gray-500 text-xs font-bold">
<div>{toPersianDigits(orderDate.toLocaleDateString('fa-IR'))}</div>
<div className="text-[10px] text-gray-400">{toPersianDigits(orderDate.toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }))}</div>
</td>
<td className="py-4 px-6 text-gray-600 font-bold">
{toPersianDigits(itemsCount)} قلم
</td>
<td className="py-4 px-6">
<select
disabled={isProcessing}
value={order.status}
onChange={(e) => handleUpdateStatus(order.id, e.target.value)}
className={`text-xs font-bold px-3 py-1.5 rounded-xl border outline-none cursor-pointer transition-all ${statusStyles[order.status]?.color || 'bg-gray-100 text-gray-700'}`}
>
<option value="pending_payment">در انتظار پرداخت / ناموفق</option>
<option value="processing">در حال پردازش</option>
<option value="shipped">ارسال شده</option>
<option value="delivered">تحویل داده شده</option>
<option value="cancelled">لغو شده</option>
</select>
</td>
<td className="py-4 px-6">
<div className="flex items-center justify-center gap-2">
<button
onClick={() => openOrderModal(order)}
className="text-purple-600 hover:bg-purple-50 p-2 rounded-xl transition-colors flex items-center gap-1 font-bold text-xs"
title="مشاهده فاکتور و جزئیات کامل"
disabled={isProcessing}
>
<Eye className="w-4 h-4" />
<span>جزئیات</span>
</button>
<button
onClick={() => handlePrintInvoice(order)}
className="text-gray-600 hover:bg-gray-100 p-2 rounded-xl transition-colors"
title="چاپ فوری فاکتور رسمی"
disabled={isProcessing}
>
<Printer className="w-4 h-4" />
</button>
</div>
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
<Pagination currentPage={page} totalPages={totalPages} onPageChange={setPage} />
</div>
{/* Admin Order Details Modal */}
{selectedOrder && (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-gray-900/60 backdrop-blur-sm">
<div className="bg-white w-full max-w-3xl rounded-3xl shadow-2xl overflow-hidden text-right border border-gray-100 max-h-[90vh] flex flex-col">
{/* Modal Header */}
<div className="bg-gray-50 px-8 py-6 flex items-center justify-between border-b border-gray-200 shrink-0">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-purple-600 rounded-2xl flex items-center justify-center text-white shadow-md">
<Package className="w-6 h-6" />
</div>
<div>
<h3 className="text-lg font-black text-gray-900 italic">
جزئیات سفارش {toPersianDigits(selectedOrder.trackingNumber || selectedOrder.id.slice(0, 8))}
</h3>
<p className="text-xs font-bold text-gray-400">
ثبت شده در {toPersianDigits(new Date(selectedOrder.createdAt || selectedOrder.date).toLocaleDateString('fa-IR'))} - ساعت {toPersianDigits(new Date(selectedOrder.createdAt || selectedOrder.date).toLocaleTimeString('fa-IR', { hour: '2-digit', minute: '2-digit' }))}
</p>
</div>
</div>
<button
onClick={() => setSelectedOrder(null)}
className="w-9 h-9 flex items-center justify-center rounded-xl bg-white border border-gray-200 text-gray-400 hover:text-red-500 transition-colors"
>
<X className="w-5 h-5" />
</button>
</div>
{/* Modal Body */}
<div className="p-8 overflow-y-auto space-y-8 custom-scrollbar">
{/* Customer & Shipping Info */}
<div className="grid md:grid-cols-2 gap-6 bg-gray-50 p-6 rounded-2xl border border-gray-100">
<div className="space-y-3">
<h4 className="text-xs font-black text-gray-400 uppercase tracking-widest flex items-center gap-1.5">
<User className="w-4 h-4 text-purple-600" />
اطلاعات خریدار
</h4>
<div className="text-sm font-black text-gray-900">
{selectedOrder.user ? `${selectedOrder.user.firstName || ''} ${selectedOrder.user.lastName || ''}` : 'خریدار مهمان'}
</div>
<div className="flex items-center gap-2 text-xs font-bold text-gray-500">
<Phone className="w-3.5 h-3.5 text-gray-400" />
<span dir="ltr">{toPersianDigits(selectedOrder.user?.phone || 'شماره ثبت نشده')}</span>
</div>
{selectedOrder.user?.email && (
<div className="flex items-center gap-2 text-xs font-bold text-gray-500">
<Mail className="w-3.5 h-3.5 text-gray-400" />
<span>{selectedOrder.user.email}</span>
</div>
)}
</div>
<div className="space-y-3">
<h4 className="text-xs font-black text-gray-400 uppercase tracking-widest flex items-center gap-1.5">
<MapPin className="w-4 h-4 text-purple-600" />
آدرس ارسال سفارش
</h4>
<p className="text-xs font-bold text-gray-700 leading-relaxed">
{selectedOrder.shippingAddress || 'آدرس ثبتی کاربر در حساب کاربری'}
</p>
</div>
</div>
{/* Status & Tracking Editor */}
<div className="bg-purple-50/50 border border-purple-100 p-6 rounded-2xl space-y-4">
<h4 className="text-xs font-black text-purple-900 uppercase tracking-widest">
مدیریت وضعیت و کد رهگیری پستی
</h4>
<div className="grid md:grid-cols-2 gap-4 items-end">
<div>
<label className="block text-xs font-bold text-gray-700 mb-1.5">تغییر وضعیت سفارش</label>
<select
value={modalStatus}
onChange={(e) => setModalStatus(e.target.value)}
className="w-full bg-white border border-gray-300 text-gray-900 text-sm font-bold rounded-xl p-2.5 outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="pending_payment">در انتظار پرداخت / ناموفق</option>
<option value="processing">در حال پردازش</option>
<option value="shipped">ارسال شده</option>
<option value="delivered">تحویل داده شده</option>
<option value="cancelled">لغو شده</option>
</select>
</div>
<div>
<label className="block text-xs font-bold text-gray-700 mb-1.5">کد رهگیری مرسوله پستی</label>
<div className="flex gap-2">
<input
type="text"
placeholder="مثلا: POST-9823478"
value={modalTrackingCode}
onChange={(e) => setModalTrackingCode(e.target.value)}
className="flex-1 bg-white border border-gray-300 text-gray-900 text-sm font-mono font-bold rounded-xl px-3 py-2.5 outline-none focus:ring-2 focus:ring-purple-500"
dir="ltr"
/>
<button
onClick={handleSaveModalChanges}
disabled={isSavingTracking}
className="bg-purple-600 text-white px-4 py-2.5 rounded-xl font-black text-xs flex items-center gap-1.5 hover:bg-purple-700 transition-all shrink-0 shadow-md shadow-purple-600/20"
>
{isSavingTracking ? <Spinner size="sm" /> : <Save className="w-4 h-4" />}
<span>ذخیره</span>
</button>
</div>
</div>
</div>
</div>
{/* Payment Gateway Transactions Log */}
{selectedOrder.paymentTransactions && selectedOrder.paymentTransactions.length > 0 && (
<div className="bg-slate-50 p-6 rounded-2xl border border-slate-200 space-y-3">
<h4 className="text-xs font-black text-slate-800 uppercase tracking-widest flex items-center gap-2">
<Clock className="w-4 h-4 text-purple-600" />
لاگ تراکنشهای درگاه پرداخت زیبال / بانکی
</h4>
<div className="space-y-2 text-xs">
{selectedOrder.paymentTransactions.map((tx) => (
<div key={tx.id} className="p-3 bg-white rounded-xl border border-slate-200 flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div className="space-y-1">
<div className="font-mono font-bold text-slate-700">
شناسه رهگیری زیبال (TrackID): <span className="text-purple-600">{tx.trackId || 'ثبت نشده'}</span>
</div>
{tx.refNumber && (
<div className="text-slate-600">
شماره ارجاع شاپرک (RRN): <span className="font-mono font-bold">{tx.refNumber}</span>
</div>
)}
{tx.message && (
<div className="text-slate-500">
پیام درگاه: <span className="font-semibold">{tx.message}</span>
</div>
)}
</div>
<div className="text-left">
<span className={`inline-block px-2.5 py-1 rounded-lg font-bold text-[11px] ${
tx.status === 'VERIFIED'
? 'bg-emerald-100 text-emerald-800'
: tx.status === 'PENDING'
? 'bg-amber-100 text-amber-800'
: 'bg-rose-100 text-rose-800'
}`}>
{tx.status === 'VERIFIED' ? 'پرداخت تایید شده' : tx.status === 'PENDING' ? 'در انتظار پرداخت' : 'پرداخت ناموفق'}
</span>
</div>
</div>
))}
</div>
</div>
)}
{/* Items Table */}
<div>
<h4 className="text-xs font-black text-gray-400 uppercase tracking-widest mb-4">
اقلام خریده شده ({toPersianDigits((selectedOrder.orderItems || selectedOrder.items || []).length)})
</h4>
<div className="space-y-3">
{(selectedOrder.orderItems || selectedOrder.items || []).map((item: OrderItem, idx: number) => {
const pName = item.product?.nameFa || item.product?.name || item.name || 'محصول کنینا';
const pImg = item.product?.imageUrl || item.product?.image || '/products/product-placeholder.png';
const pPrice = Number(item.product?.priceValue || item.priceValue || 0);
const qty = item.quantity || 1;
return (
<div key={item.id || idx} className="flex items-center justify-between p-4 border border-gray-100 rounded-2xl bg-white">
<div className="flex items-center gap-3">
<div className="w-12 h-12 bg-gray-50 rounded-xl p-1 flex items-center justify-center border border-gray-100 shrink-0">
<img src={pImg} alt={pName} className="max-w-full max-h-full object-contain" />
</div>
<div>
<div className="font-black text-sm text-gray-900">{pName}</div>
<div className="text-xs text-gray-400 font-bold">{toPersianDigits(qty)} عدد × {toPersianDigits(pPrice.toLocaleString())} تومان</div>
</div>
</div>
<div className="text-left font-black text-purple-600 text-sm">
{toPersianDigits((pPrice * qty).toLocaleString())} تومان
</div>
</div>
);
})}
</div>
</div>
{/* Financial Summary */}
<div className="bg-gray-900 text-white p-6 rounded-2xl space-y-2">
<div className="flex justify-between text-xs font-bold text-gray-400">
<span>روش پرداخت:</span>
<span className="text-white font-bold">{selectedOrder.paymentMethod === 'wallet' ? 'کیف پول الکترونیک' : 'درگاه پرداخت آنلاین'}</span>
</div>
{selectedOrder.isRefill && (
<div className="flex justify-between text-xs font-bold text-green-400 bg-green-950/50 p-2 rounded-xl border border-green-800">
<span>رزرو هوشمند تمدید دوره (۵٪ پاداش خرید بعدی):</span>
<span>ثبت و رزرو گردید</span>
</div>
)}
{Number(selectedOrder.charityDonation || 0) > 0 && (
<div className="flex justify-between text-xs font-bold text-pink-400">
<span>کمک اهدایی (ردپای مهربانی):</span>
<span>{toPersianDigits(Number(selectedOrder.charityDonation).toLocaleString())}+ تومان</span>
</div>
)}
<div className="flex justify-between text-base font-black text-amber-400 pt-3 border-t border-gray-800">
<span>مبلغ نهایی پرداخت شده:</span>
<span>{toPersianDigits(Number(selectedOrder.totalAmount || selectedOrder.total || 0).toLocaleString())} تومان</span>
</div>
</div>
</div>
{/* Modal Footer */}
<div className="p-6 bg-gray-50 border-t border-gray-200 flex gap-4 shrink-0">
<button
onClick={() => handlePrintInvoice(selectedOrder)}
className="flex-1 py-3.5 bg-purple-600 text-white rounded-xl font-black text-sm flex items-center justify-center gap-2 hover:bg-purple-700 transition-all shadow-lg shadow-purple-600/20"
>
<Download className="w-4 h-4" />
دانلود و چاپ فاکتور رسمی
</button>
<button
onClick={() => setSelectedOrder(null)}
className="px-6 py-3.5 bg-white border border-gray-200 text-gray-700 rounded-xl font-bold text-sm hover:bg-gray-100 transition-all"
>
بستن
</button>
</div>
</div>
</div>
)}
</div>
);
}