1154 lines
54 KiB
TypeScript
1154 lines
54 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
||
import {
|
||
RotateCcw,
|
||
Search,
|
||
Download,
|
||
RefreshCw,
|
||
Filter,
|
||
Calendar,
|
||
Layers,
|
||
FileSpreadsheet,
|
||
CheckCircle2,
|
||
Clock,
|
||
AlertCircle,
|
||
XCircle,
|
||
Eye,
|
||
CreditCard,
|
||
Building2,
|
||
ListOrdered,
|
||
Plus,
|
||
Send,
|
||
SlidersHorizontal,
|
||
ChevronLeft,
|
||
ChevronRight,
|
||
TrendingUp,
|
||
} from 'lucide-react';
|
||
import { toast } from 'react-hot-toast';
|
||
import api from '../services/api';
|
||
import Spinner from '../components/ui/Spinner';
|
||
import Button from '../components/ui/Button';
|
||
import Modal from '../components/ui/Modal';
|
||
|
||
|
||
export default function ZibalPortalPage() {
|
||
const [activeMainTab, setActiveMainTab] = useState<'refunds' | 'checkouts' | 'queue' | 'gateway_transactions'>('refunds');
|
||
|
||
// ==========================================
|
||
// TAB 1: REFUNDS (استرداد وجه)
|
||
// ==========================================
|
||
const [refunds, setRefunds] = useState<any[]>([]);
|
||
const [loadingRefunds, setLoadingRefunds] = useState(false);
|
||
const [refundSearchOpen, setRefundSearchOpen] = useState(false);
|
||
const [refundFilters, setRefundFilters] = useState({
|
||
status: '',
|
||
psp: '',
|
||
fromDate: '',
|
||
toDate: '',
|
||
trackId: '',
|
||
refundId: '',
|
||
orderId: '',
|
||
description: '',
|
||
name: '',
|
||
destination: '',
|
||
refNumber: '',
|
||
page: 1,
|
||
size: 20,
|
||
});
|
||
|
||
// New Refund Modal
|
||
const [newRefundModal, setNewRefundModal] = useState(false);
|
||
const [refundForm, setRefundForm] = useState({
|
||
trackId: '',
|
||
amount: '',
|
||
tryReverse: true,
|
||
cardNumber: '',
|
||
description: '',
|
||
});
|
||
const [isSubmittingRefund, setIsSubmittingRefund] = useState(false);
|
||
|
||
// ==========================================
|
||
// TAB 2: CHECKOUTS / PAYOUTS (گزارش واریز / تسویه)
|
||
// ==========================================
|
||
const [checkouts, setCheckouts] = useState<any[]>([]);
|
||
const [loadingCheckouts, setLoadingCheckouts] = useState(false);
|
||
const [checkoutSearchOpen, setCheckoutSearchOpen] = useState(false);
|
||
const [checkoutFilters, setCheckoutFilters] = useState({
|
||
fromDate: '',
|
||
toDate: '',
|
||
status: '',
|
||
type: '',
|
||
refNumber: '',
|
||
destinationAccount: '',
|
||
merchantName: '',
|
||
page: 1,
|
||
size: 20,
|
||
});
|
||
|
||
// ==========================================
|
||
// TAB 3: CHECKOUT QUEUE (صف تسویه)
|
||
// ==========================================
|
||
const [queueItems, setQueueItems] = useState<any[]>([]);
|
||
const [loadingQueue, setLoadingQueue] = useState(false);
|
||
|
||
// ==========================================
|
||
// TAB 4: DIRECT GATEWAY TRANSACTIONS (تراکنشهای درگاه)
|
||
// ==========================================
|
||
const [gatewayTxList, setGatewayTxList] = useState<any[]>([]);
|
||
const [loadingGatewayTx, setLoadingGatewayTx] = useState(false);
|
||
const [gatewaySearchOpen, setGatewaySearchOpen] = useState(false);
|
||
const [gatewayFilters, setGatewayFilters] = useState({
|
||
fromDate: '',
|
||
toDate: '',
|
||
status: '',
|
||
trackId: '',
|
||
orderId: '',
|
||
mobile: '',
|
||
cardNumber: '',
|
||
amount: '',
|
||
page: 1,
|
||
size: 20,
|
||
});
|
||
|
||
// Details Modal
|
||
const [detailModalItem, setDetailModalItem] = useState<{ title: string; data: any } | null>(null);
|
||
|
||
// -------------------------------------------------------------
|
||
// Data Fetching Handlers
|
||
// -------------------------------------------------------------
|
||
|
||
const fetchRefunds = useCallback(async () => {
|
||
try {
|
||
setLoadingRefunds(true);
|
||
const res = await api.post('/payment/admin/refund/list', {
|
||
page: refundFilters.page,
|
||
size: refundFilters.size,
|
||
fromDate: refundFilters.fromDate || undefined,
|
||
toDate: refundFilters.toDate || undefined,
|
||
status: refundFilters.status ? [Number(refundFilters.status)] : undefined,
|
||
});
|
||
|
||
let items = [];
|
||
if (Array.isArray(res.data)) items = res.data;
|
||
else if (res.data?.data && Array.isArray(res.data.data)) items = res.data.data;
|
||
else if (res.data?.refundList && Array.isArray(res.data.refundList)) items = res.data.refundList;
|
||
|
||
// Client-side quick filter matching user screenshot search fields
|
||
if (refundFilters.trackId) items = items.filter((x: any) => String(x.trackId || x.transactionTrackId || '').includes(refundFilters.trackId));
|
||
if (refundFilters.refundId) items = items.filter((x: any) => String(x.id || x.refundId || '').includes(refundFilters.refundId));
|
||
if (refundFilters.orderId) items = items.filter((x: any) => String(x.orderId || '').includes(refundFilters.orderId));
|
||
if (refundFilters.refNumber) items = items.filter((x: any) => String(x.refNumber || '').includes(refundFilters.refNumber));
|
||
if (refundFilters.name) items = items.filter((x: any) => String(x.name || x.cardHolder || '').includes(refundFilters.name));
|
||
if (refundFilters.destination) items = items.filter((x: any) => String(x.cardNumber || x.bankAccount || x.iban || '').includes(refundFilters.destination));
|
||
|
||
setRefunds(items);
|
||
} catch (e: any) {
|
||
toast.error(e?.response?.data?.message || 'خطا در دریافت لیست استردادها از زیبال');
|
||
} finally {
|
||
setLoadingRefunds(false);
|
||
}
|
||
}, [refundFilters]);
|
||
|
||
const fetchCheckouts = useCallback(async () => {
|
||
try {
|
||
setLoadingCheckouts(true);
|
||
const res = await api.post('/payment/admin/checkout-report', {
|
||
page: checkoutFilters.page,
|
||
size: checkoutFilters.size,
|
||
fromDate: checkoutFilters.fromDate || undefined,
|
||
toDate: checkoutFilters.toDate || undefined,
|
||
});
|
||
|
||
let items = [];
|
||
if (Array.isArray(res.data)) items = res.data;
|
||
else if (res.data?.data && Array.isArray(res.data.data)) items = res.data.data;
|
||
else if (res.data?.checkoutList && Array.isArray(res.data.checkoutList)) items = res.data.checkoutList;
|
||
|
||
if (checkoutFilters.refNumber) items = items.filter((x: any) => String(x.refNumber || x.referenceId || '').includes(checkoutFilters.refNumber));
|
||
if (checkoutFilters.destinationAccount) items = items.filter((x: any) => String(x.accountIban || x.iban || x.destination || '').includes(checkoutFilters.destinationAccount));
|
||
|
||
setCheckouts(items);
|
||
} catch (e: any) {
|
||
toast.error(e?.response?.data?.message || 'خطا در دریافت گزارش تسویهها از زیبال');
|
||
} finally {
|
||
setLoadingCheckouts(false);
|
||
}
|
||
}, [checkoutFilters]);
|
||
|
||
const fetchQueue = async () => {
|
||
try {
|
||
setLoadingQueue(true);
|
||
const res = await api.get('/payment/admin/checkout-queue');
|
||
const items = Array.isArray(res.data) ? res.data : (res.data?.data || []);
|
||
setQueueItems(items);
|
||
} catch (e: any) {
|
||
toast.error('خطا در دریافت صف تسویههای در انتظار');
|
||
} finally {
|
||
setLoadingQueue(false);
|
||
}
|
||
};
|
||
|
||
const fetchGatewayTx = useCallback(async () => {
|
||
try {
|
||
setLoadingGatewayTx(true);
|
||
const res = await api.post('/payment/admin/gateway-report', {
|
||
page: gatewayFilters.page,
|
||
size: gatewayFilters.size,
|
||
fromDate: gatewayFilters.fromDate || undefined,
|
||
toDate: gatewayFilters.toDate || undefined,
|
||
trackId: gatewayFilters.trackId ? Number(gatewayFilters.trackId) : undefined,
|
||
orderId: gatewayFilters.orderId || undefined,
|
||
mobile: gatewayFilters.mobile || undefined,
|
||
cardNumber: gatewayFilters.cardNumber || undefined,
|
||
status: gatewayFilters.status ? Number(gatewayFilters.status) : undefined,
|
||
});
|
||
|
||
let items = [];
|
||
if (Array.isArray(res.data)) items = res.data;
|
||
else if (res.data?.data && Array.isArray(res.data.data)) items = res.data.data;
|
||
else if (res.data?.transactions && Array.isArray(res.data.transactions)) items = res.data.transactions;
|
||
|
||
setGatewayTxList(items);
|
||
} catch (e: any) {
|
||
toast.error('خطا در دریافت لیست تراکنشهای زیبال');
|
||
} finally {
|
||
setLoadingGatewayTx(false);
|
||
}
|
||
}, [gatewayFilters]);
|
||
|
||
useEffect(() => {
|
||
if (activeMainTab === 'refunds') fetchRefunds();
|
||
if (activeMainTab === 'checkouts') fetchCheckouts();
|
||
if (activeMainTab === 'queue') fetchQueue();
|
||
if (activeMainTab === 'gateway_transactions') fetchGatewayTx();
|
||
}, [activeMainTab, fetchRefunds, fetchCheckouts, fetchGatewayTx]);
|
||
|
||
// -------------------------------------------------------------
|
||
// Actions
|
||
// -------------------------------------------------------------
|
||
|
||
const handleCreateRefundSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
if (!refundForm.trackId) {
|
||
toast.error('شناسه تراکنش (Track ID) الزامی است');
|
||
return;
|
||
}
|
||
try {
|
||
setIsSubmittingRefund(true);
|
||
const res = await api.post('/payment/admin/refund', {
|
||
trackId: refundForm.trackId,
|
||
amount: refundForm.amount ? Number(refundForm.amount) * 10 : undefined, // Convert to Rials
|
||
tryReverse: refundForm.tryReverse,
|
||
cardNumber: refundForm.cardNumber || undefined,
|
||
description: refundForm.description || undefined,
|
||
});
|
||
|
||
if (res.data?.result === 1 || res.data?.status === 1 || res.data?.success) {
|
||
toast.success(res.data?.message || 'درخواست استرداد با موفقیت به زیبال ارسال شد.');
|
||
setNewRefundModal(false);
|
||
setRefundForm({ trackId: '', amount: '', tryReverse: true, cardNumber: '', description: '' });
|
||
fetchRefunds();
|
||
} else {
|
||
toast.error(res.data?.message || 'خطا در ثبت استرداد وجه');
|
||
}
|
||
} catch (e: any) {
|
||
toast.error(e?.response?.data?.message || 'خطا در برقراری ارتباط با سرویس استرداد زیبال');
|
||
} finally {
|
||
setIsSubmittingRefund(false);
|
||
}
|
||
};
|
||
|
||
const handleInquireRefundStatus = async (item: any) => {
|
||
try {
|
||
const res = await api.post('/payment/admin/refund/inquiry', {
|
||
refundId: item.id || item.refundId,
|
||
transactionTrackId: item.trackId || item.transactionTrackId,
|
||
});
|
||
setDetailModalItem({
|
||
title: `استعلام استرداد #${item.id || item.trackId}`,
|
||
data: res.data,
|
||
});
|
||
toast.success('استعلام با موفقیت انجام شد');
|
||
} catch (e: any) {
|
||
toast.error(e?.response?.data?.message || 'خطا در استعلام');
|
||
}
|
||
};
|
||
|
||
const downloadCSV = (data: any[], filename: string) => {
|
||
if (!data || data.length === 0) {
|
||
toast.error('دادهای برای خروجی اکسل یافت نشد');
|
||
return;
|
||
}
|
||
const headers = Object.keys(data[0]).join(',');
|
||
const rows = data.map((row) =>
|
||
Object.values(row)
|
||
.map((v) => `"${String(v ?? '').replace(/"/g, '""')}"`)
|
||
.join(',')
|
||
);
|
||
const csvContent = '\uFEFF' + [headers, ...rows].join('\n');
|
||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
||
const url = URL.createObjectURL(blob);
|
||
const link = document.createElement('a');
|
||
link.setAttribute('href', url);
|
||
link.setAttribute('download', `${filename}-${new Date().toISOString().split('T')[0]}.csv`);
|
||
document.body.appendChild(link);
|
||
link.click();
|
||
document.body.removeChild(link);
|
||
toast.success('فایل گزارش با موفقیت دانلود شد');
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6 font-vazir" dir="rtl">
|
||
{/* Top 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">
|
||
<RotateCcw className="w-7 h-7 text-indigo-600" />
|
||
پرتال و داشبورد خدمات مالی زیبال
|
||
</h2>
|
||
<p className="text-gray-500 font-medium text-xs sm:text-sm mt-1">
|
||
مشاهده گزارشات استرداد وجه (Refund/Reverse)، تسویهها و واریزها، صف تسویه و تراکنشهای رسمی شاپرک
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-3">
|
||
<button
|
||
onClick={() => setNewRefundModal(true)}
|
||
className="bg-rose-600 hover:bg-rose-700 text-white font-bold text-xs sm:text-sm px-4 py-2.5 rounded-xl transition-colors shadow-md shadow-rose-200 flex items-center gap-2 cursor-pointer"
|
||
>
|
||
<Plus className="w-4 h-4" />
|
||
<span>ثبت استرداد وجه جدید</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Main Feature Tabs (Matching Zibal Panel layout) */}
|
||
<div className="bg-white p-2 rounded-2xl shadow-sm border border-gray-200 flex items-center justify-between overflow-x-auto gap-2 no-scrollbar">
|
||
<div className="flex items-center gap-2 shrink-0">
|
||
<button
|
||
onClick={() => setActiveMainTab('refunds')}
|
||
className={`px-5 py-2.5 rounded-xl font-black text-xs sm:text-sm transition-all flex items-center gap-2 cursor-pointer whitespace-nowrap ${
|
||
activeMainTab === 'refunds'
|
||
? 'bg-indigo-600 text-white shadow-md shadow-indigo-200'
|
||
: 'text-gray-600 hover:bg-gray-100'
|
||
}`}
|
||
>
|
||
<RotateCcw className="w-4 h-4 shrink-0" />
|
||
<span>استرداد وجه</span>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setActiveMainTab('checkouts')}
|
||
className={`px-5 py-2.5 rounded-xl font-black text-xs sm:text-sm transition-all flex items-center gap-2 cursor-pointer whitespace-nowrap ${
|
||
activeMainTab === 'checkouts'
|
||
? 'bg-indigo-600 text-white shadow-md shadow-indigo-200'
|
||
: 'text-gray-600 hover:bg-gray-100'
|
||
}`}
|
||
>
|
||
<CreditCard className="w-4 h-4 shrink-0" />
|
||
<span>گزارش واریز و تسویه</span>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setActiveMainTab('queue')}
|
||
className={`px-5 py-2.5 rounded-xl font-black text-xs sm:text-sm transition-all flex items-center gap-2 cursor-pointer whitespace-nowrap ${
|
||
activeMainTab === 'queue'
|
||
? 'bg-indigo-600 text-white shadow-md shadow-indigo-200'
|
||
: 'text-gray-600 hover:bg-gray-100'
|
||
}`}
|
||
>
|
||
<ListOrdered className="w-4 h-4 shrink-0" />
|
||
<span>صف تسویه</span>
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => setActiveMainTab('gateway_transactions')}
|
||
className={`px-5 py-2.5 rounded-xl font-black text-xs sm:text-sm transition-all flex items-center gap-2 cursor-pointer whitespace-nowrap ${
|
||
activeMainTab === 'gateway_transactions'
|
||
? 'bg-indigo-600 text-white shadow-md shadow-indigo-200'
|
||
: 'text-gray-600 hover:bg-gray-100'
|
||
}`}
|
||
>
|
||
<TrendingUp className="w-4 h-4 shrink-0" />
|
||
<span>تراکنشهای درگاه (IPG)</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ======================================================== */}
|
||
{/* 1. TAB CONTENT: REFUNDS (استرداد وجه) */}
|
||
{/* ======================================================== */}
|
||
{activeMainTab === 'refunds' && (
|
||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 space-y-6">
|
||
{/* Top Bar with Filter & Search Controls */}
|
||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b border-gray-100 pb-5">
|
||
<div className="flex items-center gap-2.5">
|
||
<Button
|
||
variant="primary"
|
||
size="sm"
|
||
startIcon={Download}
|
||
onClick={() => downloadCSV(refunds, 'zibal-refunds')}
|
||
>
|
||
دانلود گزارش
|
||
</Button>
|
||
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={fetchRefunds}
|
||
isLoading={loadingRefunds}
|
||
startIcon={RefreshCw}
|
||
title="بروزرسانی"
|
||
/>
|
||
</div>
|
||
|
||
|
||
{/* Filter Inputs Bar */}
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
{/* Date Filter */}
|
||
<div className="flex items-center gap-1.5 bg-gray-50 border border-gray-200 px-3 py-1.5 rounded-xl text-xs font-bold">
|
||
<Calendar className="w-3.5 h-3.5 text-indigo-600" />
|
||
<span>تاریخ از:</span>
|
||
<input
|
||
type="date"
|
||
value={refundFilters.fromDate}
|
||
onChange={(e) => setRefundFilters({ ...refundFilters, fromDate: e.target.value })}
|
||
className="bg-transparent outline-none font-mono text-[11px]"
|
||
/>
|
||
<span>تا:</span>
|
||
<input
|
||
type="date"
|
||
value={refundFilters.toDate}
|
||
onChange={(e) => setRefundFilters({ ...refundFilters, toDate: e.target.value })}
|
||
className="bg-transparent outline-none font-mono text-[11px]"
|
||
/>
|
||
</div>
|
||
|
||
{/* Status Selector */}
|
||
<select
|
||
value={refundFilters.status}
|
||
onChange={(e) => setRefundFilters({ ...refundFilters, status: e.target.value })}
|
||
className="bg-gray-50 border border-gray-200 px-3 py-2 rounded-xl text-xs font-bold outline-none"
|
||
>
|
||
<option value="">همه وضعیتها</option>
|
||
<option value="1">موفق (تایید شده)</option>
|
||
<option value="0">در انتظار انجام</option>
|
||
<option value="-1">ناموفق / لغو شده</option>
|
||
</select>
|
||
|
||
{/* Advanced Search Popup Toggle */}
|
||
<div className="relative">
|
||
<button
|
||
onClick={() => setRefundSearchOpen(!refundSearchOpen)}
|
||
className="border border-gray-200 bg-gray-50 hover:bg-gray-100 px-4 py-2 rounded-xl text-xs font-bold flex items-center gap-2 cursor-pointer"
|
||
>
|
||
<Search className="w-3.5 h-3.5 text-gray-500" />
|
||
<span>جستجوی پیشرفته</span>
|
||
</button>
|
||
|
||
{/* Advanced Search Dropdown Window (Matching Exact User Screenshot) */}
|
||
{refundSearchOpen && (
|
||
<div className="absolute left-0 mt-2 w-72 bg-white rounded-2xl shadow-2xl border border-gray-200 p-4 space-y-3 z-30 font-vazir text-xs">
|
||
<div className="space-y-2.5">
|
||
<div className="relative">
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی شماره تراکنش (Track ID)"
|
||
value={refundFilters.trackId}
|
||
onChange={(e) => setRefundFilters({ ...refundFilters, trackId: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 pr-3 pl-8 text-xs outline-none focus:ring-1 focus:ring-indigo-500"
|
||
/>
|
||
<Search className="w-3.5 h-3.5 text-gray-400 absolute left-2.5 top-3" />
|
||
</div>
|
||
|
||
<div className="relative">
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی شناسه استرداد (Refund ID)"
|
||
value={refundFilters.refundId}
|
||
onChange={(e) => setRefundFilters({ ...refundFilters, refundId: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 pr-3 pl-8 text-xs outline-none focus:ring-1 focus:ring-indigo-500"
|
||
/>
|
||
<Search className="w-3.5 h-3.5 text-gray-400 absolute left-2.5 top-3" />
|
||
</div>
|
||
|
||
<div className="relative">
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی شناسه سفارش (Order ID)"
|
||
value={refundFilters.orderId}
|
||
onChange={(e) => setRefundFilters({ ...refundFilters, orderId: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 pr-3 pl-8 text-xs outline-none focus:ring-1 focus:ring-indigo-500"
|
||
/>
|
||
<Search className="w-3.5 h-3.5 text-gray-400 absolute left-2.5 top-3" />
|
||
</div>
|
||
|
||
<div className="relative">
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی نام صاحب حساب"
|
||
value={refundFilters.name}
|
||
onChange={(e) => setRefundFilters({ ...refundFilters, name: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 pr-3 pl-8 text-xs outline-none focus:ring-1 focus:ring-indigo-500"
|
||
/>
|
||
<Search className="w-3.5 h-3.5 text-gray-400 absolute left-2.5 top-3" />
|
||
</div>
|
||
|
||
<div className="relative">
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی مقصد (شماره کارت / شبا)"
|
||
value={refundFilters.destination}
|
||
onChange={(e) => setRefundFilters({ ...refundFilters, destination: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 pr-3 pl-8 text-xs outline-none focus:ring-1 focus:ring-indigo-500"
|
||
/>
|
||
<Search className="w-3.5 h-3.5 text-gray-400 absolute left-2.5 top-3" />
|
||
</div>
|
||
|
||
<div className="relative">
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی شماره مرجع بانکی (RRN)"
|
||
value={refundFilters.refNumber}
|
||
onChange={(e) => setRefundFilters({ ...refundFilters, refNumber: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 pr-3 pl-8 text-xs outline-none focus:ring-1 focus:ring-indigo-500"
|
||
/>
|
||
<Search className="w-3.5 h-3.5 text-gray-400 absolute left-2.5 top-3" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between pt-2 border-t border-gray-100">
|
||
<button
|
||
onClick={() => {
|
||
setRefundFilters({
|
||
...refundFilters,
|
||
trackId: '',
|
||
refundId: '',
|
||
orderId: '',
|
||
description: '',
|
||
name: '',
|
||
destination: '',
|
||
refNumber: '',
|
||
});
|
||
}}
|
||
className="text-indigo-600 hover:text-indigo-800 font-bold text-xs cursor-pointer"
|
||
>
|
||
پاک کردن فرم
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => {
|
||
setRefundSearchOpen(false);
|
||
fetchRefunds();
|
||
}}
|
||
className="bg-indigo-600 hover:bg-indigo-700 text-white font-bold px-4 py-2 rounded-xl flex items-center gap-1.5 cursor-pointer shadow-md shadow-indigo-100"
|
||
>
|
||
<Search className="w-3 h-3" />
|
||
<span>جستجو</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Refund Table */}
|
||
{loadingRefunds ? (
|
||
<div className="py-20 flex justify-center">
|
||
<Spinner size="lg" className="text-indigo-600" />
|
||
</div>
|
||
) : refunds.length === 0 ? (
|
||
<div className="py-16 text-center text-gray-400 space-y-3">
|
||
<div className="w-14 h-14 bg-gray-50 border border-gray-100 rounded-2xl flex items-center justify-center mx-auto text-gray-300">
|
||
<RotateCcw className="w-6 h-6" />
|
||
</div>
|
||
<p className="text-xs font-bold">نتیجهای یافت نشد.</p>
|
||
</div>
|
||
) : (
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full text-right border-collapse text-xs">
|
||
<thead>
|
||
<tr className="bg-gray-50/80 border-b border-gray-200 text-[11px] font-black text-gray-600">
|
||
<th className="p-3.5">شناسه استرداد</th>
|
||
<th className="p-3.5">شماره تراکنش</th>
|
||
<th className="p-3.5">شناسه سفارش</th>
|
||
<th className="p-3.5">شماره مرجع</th>
|
||
<th className="p-3.5">نام / دارنده</th>
|
||
<th className="p-3.5">مقصد</th>
|
||
<th className="p-3.5">مبلغ استرداد</th>
|
||
<th className="p-3.5">نوع / کارمزد</th>
|
||
<th className="p-3.5">وضعیت</th>
|
||
<th className="p-3.5">تاریخ ایجاد</th>
|
||
<th className="p-3.5 text-center">عملیات</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-gray-100 font-bold">
|
||
{refunds.map((rf: any, i: number) => (
|
||
<tr key={rf.id || i} className="hover:bg-gray-50/70 transition-colors">
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-800">{rf.id || rf.refundId || '-'}</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-indigo-700">{rf.trackId || rf.transactionTrackId || '-'}</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-600">{rf.orderId || '-'}</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-emerald-700">{rf.refNumber || '-'}</td>
|
||
<td className="p-3.5 text-gray-900">{rf.name || rf.cardHolder || '-'}</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-700">{rf.cardNumber || rf.bankAccount || '-'}</td>
|
||
<td className="p-3.5 font-black text-gray-900 font-mono">
|
||
{(Number(rf.amount || 0) / 10).toLocaleString()} تومان
|
||
</td>
|
||
<td className="p-3.5 text-[11px] text-gray-500">
|
||
<span className="bg-slate-100 text-slate-700 px-2 py-0.5 rounded">
|
||
{rf.tryReverse ? 'Reverse' : 'Refund'}
|
||
</span>
|
||
</td>
|
||
<td className="p-3.5">
|
||
<span
|
||
className={`px-2.5 py-1 rounded-full text-[10px] font-black inline-flex items-center gap-1 ${
|
||
rf.status === 1 || rf.status === 'SUCCESS'
|
||
? 'bg-emerald-50 text-emerald-700 border border-emerald-200'
|
||
: rf.status === 0 || rf.status === 'PENDING'
|
||
? 'bg-amber-50 text-amber-700 border border-amber-200'
|
||
: 'bg-rose-50 text-rose-700 border border-rose-200'
|
||
}`}
|
||
>
|
||
{rf.status === 1 || rf.status === 'SUCCESS' ? 'موفق' : rf.status === 0 || rf.status === 'PENDING' ? 'در صف' : 'ناموفق'}
|
||
</span>
|
||
</td>
|
||
<td className="p-3.5 text-[11px] text-gray-500 font-mono">
|
||
{rf.createdAt ? new Date(rf.createdAt).toLocaleDateString('fa-IR') : '-'}
|
||
</td>
|
||
<td className="p-3.5 text-center">
|
||
<button
|
||
onClick={() => handleInquireRefundStatus(rf)}
|
||
className="p-1.5 bg-indigo-50 hover:bg-indigo-100 text-indigo-600 rounded-lg transition-colors cursor-pointer"
|
||
title="استعلام آخرین وضعیت از زیبال"
|
||
>
|
||
<Eye className="w-3.5 h-3.5" />
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* ======================================================== */}
|
||
{/* 2. TAB CONTENT: CHECKOUTS / PAYOUTS (گزارش واریز) */}
|
||
{/* ======================================================== */}
|
||
{activeMainTab === 'checkouts' && (
|
||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 space-y-6">
|
||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b border-gray-100 pb-5">
|
||
<div className="flex items-center gap-2.5">
|
||
<Button
|
||
variant="primary"
|
||
size="sm"
|
||
startIcon={Download}
|
||
onClick={() => downloadCSV(checkouts, 'zibal-checkouts')}
|
||
>
|
||
دانلود گزارش
|
||
</Button>
|
||
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={fetchCheckouts}
|
||
isLoading={loadingCheckouts}
|
||
startIcon={RefreshCw}
|
||
title="بروزرسانی"
|
||
/>
|
||
</div>
|
||
|
||
|
||
{/* Filter Inputs Bar */}
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<div className="flex items-center gap-1.5 bg-gray-50 border border-gray-200 px-3 py-1.5 rounded-xl text-xs font-bold">
|
||
<Calendar className="w-3.5 h-3.5 text-indigo-600" />
|
||
<span>از:</span>
|
||
<input
|
||
type="date"
|
||
value={checkoutFilters.fromDate}
|
||
onChange={(e) => setCheckoutFilters({ ...checkoutFilters, fromDate: e.target.value })}
|
||
className="bg-transparent outline-none font-mono text-[11px]"
|
||
/>
|
||
<span>تا:</span>
|
||
<input
|
||
type="date"
|
||
value={checkoutFilters.toDate}
|
||
onChange={(e) => setCheckoutFilters({ ...checkoutFilters, toDate: e.target.value })}
|
||
className="bg-transparent outline-none font-mono text-[11px]"
|
||
/>
|
||
</div>
|
||
|
||
{/* Advanced Checkout Search Popup */}
|
||
<div className="relative">
|
||
<button
|
||
onClick={() => setCheckoutSearchOpen(!checkoutSearchOpen)}
|
||
className="border border-gray-200 bg-gray-50 hover:bg-gray-100 px-4 py-2 rounded-xl text-xs font-bold flex items-center gap-2 cursor-pointer"
|
||
>
|
||
<Search className="w-3.5 h-3.5 text-gray-500" />
|
||
<span>جستجو</span>
|
||
</button>
|
||
|
||
{checkoutSearchOpen && (
|
||
<div className="absolute left-0 mt-2 w-72 bg-white rounded-2xl shadow-2xl border border-gray-200 p-4 space-y-3 z-30 font-vazir text-xs">
|
||
<div className="space-y-2.5">
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی شناسه مرجع"
|
||
value={checkoutFilters.refNumber}
|
||
onChange={(e) => setCheckoutFilters({ ...checkoutFilters, refNumber: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 text-xs outline-none focus:ring-1 focus:ring-indigo-500"
|
||
/>
|
||
<input
|
||
type="text"
|
||
placeholder="جستجوی حساب مقصد (شبا)"
|
||
value={checkoutFilters.destinationAccount}
|
||
onChange={(e) => setCheckoutFilters({ ...checkoutFilters, destinationAccount: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 text-xs outline-none focus:ring-1 focus:ring-indigo-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between pt-2 border-t border-gray-100">
|
||
<button
|
||
onClick={() => setCheckoutFilters({ ...checkoutFilters, refNumber: '', destinationAccount: '' })}
|
||
className="text-indigo-600 hover:text-indigo-800 font-bold text-xs cursor-pointer"
|
||
>
|
||
پاک کردن فرم
|
||
</button>
|
||
<button
|
||
onClick={() => {
|
||
setCheckoutSearchOpen(false);
|
||
fetchCheckouts();
|
||
}}
|
||
className="bg-indigo-600 hover:bg-indigo-700 text-white font-bold px-4 py-2 rounded-xl flex items-center gap-1.5 cursor-pointer"
|
||
>
|
||
<Search className="w-3 h-3" />
|
||
<span>جستجو</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Table */}
|
||
{loadingCheckouts ? (
|
||
<div className="py-20 flex justify-center">
|
||
<Spinner size="lg" className="text-indigo-600" />
|
||
</div>
|
||
) : checkouts.length === 0 ? (
|
||
<div className="py-16 text-center text-gray-400 space-y-3">
|
||
<div className="w-14 h-14 bg-gray-50 border border-gray-100 rounded-2xl flex items-center justify-center mx-auto text-gray-300">
|
||
<CreditCard className="w-6 h-6" />
|
||
</div>
|
||
<p className="text-xs font-bold">نتیجهای یافت نشد.</p>
|
||
</div>
|
||
) : (
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full text-right border-collapse text-xs">
|
||
<thead>
|
||
<tr className="bg-gray-50/80 border-b border-gray-200 text-[11px] font-black text-gray-600">
|
||
<th className="p-3.5">حساب مقصد</th>
|
||
<th className="p-3.5">نام صاحب حساب</th>
|
||
<th className="p-3.5">مبلغ کل (تومان)</th>
|
||
<th className="p-3.5">نوع تسویه</th>
|
||
<th className="p-3.5">تاریخ واریز</th>
|
||
<th className="p-3.5">شناسه مرجع</th>
|
||
<th className="p-3.5">وضعیت</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-gray-100 font-bold">
|
||
{checkouts.map((chk: any, i: number) => (
|
||
<tr key={chk.id || i} className="hover:bg-gray-50/70 transition-colors">
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-900">{chk.accountIban || chk.iban || chk.destination || '-'}</td>
|
||
<td className="p-3.5 text-gray-900">{chk.accountName || chk.name || '-'}</td>
|
||
<td className="p-3.5 font-black text-emerald-700 font-mono">
|
||
{(Number(chk.amount || 0) / 10).toLocaleString()} تومان
|
||
</td>
|
||
<td className="p-3.5 text-[11px] text-gray-500">{chk.type || 'پایا شاپرکی'}</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-500">
|
||
{chk.paidAt || chk.createdAt ? new Date(chk.paidAt || chk.createdAt).toLocaleDateString('fa-IR') : '-'}
|
||
</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-indigo-700">{chk.refNumber || chk.referenceId || '-'}</td>
|
||
<td className="p-3.5">
|
||
<span className="bg-emerald-50 text-emerald-700 border border-emerald-200 px-2.5 py-1 rounded-full text-[10px] font-black">
|
||
واریز شده
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* ======================================================== */}
|
||
{/* 3. TAB CONTENT: QUEUE (صف تسویه) */}
|
||
{/* ======================================================== */}
|
||
{activeMainTab === 'queue' && (
|
||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 space-y-4">
|
||
<div className="flex items-center justify-between border-b border-gray-100 pb-4">
|
||
<h3 className="font-black text-sm text-gray-900 flex items-center gap-2">
|
||
<ListOrdered className="w-4 h-4 text-indigo-600" />
|
||
<span>لیست تسویهها و واریزیهای در انتظار تایید شاپرک</span>
|
||
</h3>
|
||
<button
|
||
onClick={fetchQueue}
|
||
disabled={loadingQueue}
|
||
className="text-xs font-bold text-indigo-600 hover:text-indigo-800 flex items-center gap-1 cursor-pointer"
|
||
>
|
||
<RefreshCw className={`w-3.5 h-3.5 ${loadingQueue ? 'animate-spin' : ''}`} />
|
||
<span>بروزرسانی صف</span>
|
||
</button>
|
||
</div>
|
||
|
||
{loadingQueue ? (
|
||
<div className="py-20 flex justify-center">
|
||
<Spinner size="lg" className="text-indigo-600" />
|
||
</div>
|
||
) : queueItems.length === 0 ? (
|
||
<div className="py-12 text-center text-gray-400 space-y-2">
|
||
<CheckCircle2 className="w-10 h-10 text-emerald-500 mx-auto" />
|
||
<p className="text-xs font-bold text-gray-700">صف تسویه خالی است. کلیه مبالغ تسویه شدهاند.</p>
|
||
</div>
|
||
) : (
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full text-right border-collapse text-xs">
|
||
<thead>
|
||
<tr className="bg-gray-50/80 border-b border-gray-200 text-[11px] font-black text-gray-600">
|
||
<th className="p-3.5">شناسه درخواست</th>
|
||
<th className="p-3.5">حساب مقصد (شبا)</th>
|
||
<th className="p-3.5">مبلغ در صف</th>
|
||
<th className="p-3.5">زمان ثبت در صف</th>
|
||
<th className="p-3.5">وضعیت</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-gray-100 font-bold">
|
||
{queueItems.map((q: any, i: number) => (
|
||
<tr key={q.id || i} className="hover:bg-gray-50/70">
|
||
<td className="p-3.5 font-mono text-[11px]">{q.id || q.requestId || '-'}</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-800">{q.iban || q.accountIban || '-'}</td>
|
||
<td className="p-3.5 font-black text-indigo-700 font-mono">
|
||
{(Number(q.amount || 0) / 10).toLocaleString()} تومان
|
||
</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-500">
|
||
{q.createdAt ? new Date(q.createdAt).toLocaleDateString('fa-IR') : '-'}
|
||
</td>
|
||
<td className="p-3.5">
|
||
<span className="bg-amber-50 text-amber-700 border border-amber-200 px-2.5 py-1 rounded-full text-[10px] font-black">
|
||
در انتظار سیکل پایا
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* ======================================================== */}
|
||
{/* 4. TAB CONTENT: GATEWAY TRANSACTIONS (تراکنشهای درگاه) */}
|
||
{/* ======================================================== */}
|
||
{activeMainTab === 'gateway_transactions' && (
|
||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6 space-y-6">
|
||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 border-b border-gray-100 pb-5">
|
||
<div className="flex items-center gap-2.5">
|
||
<Button
|
||
variant="primary"
|
||
size="sm"
|
||
startIcon={Download}
|
||
onClick={() => downloadCSV(gatewayTxList, 'zibal-ipg-transactions')}
|
||
>
|
||
دانلود گزارش درگاه
|
||
</Button>
|
||
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={fetchGatewayTx}
|
||
isLoading={loadingGatewayTx}
|
||
startIcon={RefreshCw}
|
||
title="بروزرسانی"
|
||
/>
|
||
</div>
|
||
|
||
|
||
{/* Filter Inputs */}
|
||
<div className="flex flex-wrap items-center gap-3">
|
||
<div className="flex items-center gap-1.5 bg-gray-50 border border-gray-200 px-3 py-1.5 rounded-xl text-xs font-bold">
|
||
<Calendar className="w-3.5 h-3.5 text-indigo-600" />
|
||
<span>از:</span>
|
||
<input
|
||
type="date"
|
||
value={gatewayFilters.fromDate}
|
||
onChange={(e) => setGatewayFilters({ ...gatewayFilters, fromDate: e.target.value })}
|
||
className="bg-transparent outline-none font-mono text-[11px]"
|
||
/>
|
||
<span>تا:</span>
|
||
<input
|
||
type="date"
|
||
value={gatewayFilters.toDate}
|
||
onChange={(e) => setGatewayFilters({ ...gatewayFilters, toDate: e.target.value })}
|
||
className="bg-transparent outline-none font-mono text-[11px]"
|
||
/>
|
||
</div>
|
||
|
||
{/* Advanced Gateway Search Popup */}
|
||
<div className="relative">
|
||
<button
|
||
onClick={() => setGatewaySearchOpen(!gatewaySearchOpen)}
|
||
className="border border-gray-200 bg-gray-50 hover:bg-gray-100 px-4 py-2 rounded-xl text-xs font-bold flex items-center gap-2 cursor-pointer"
|
||
>
|
||
<Search className="w-3.5 h-3.5 text-gray-500" />
|
||
<span>جستجو و فیلتر تراکنش</span>
|
||
</button>
|
||
|
||
{gatewaySearchOpen && (
|
||
<div className="absolute left-0 mt-2 w-72 bg-white rounded-2xl shadow-2xl border border-gray-200 p-4 space-y-3 z-30 font-vazir text-xs">
|
||
<div className="space-y-2.5">
|
||
<input
|
||
type="text"
|
||
placeholder="شماره تراکنش (Track ID)"
|
||
value={gatewayFilters.trackId}
|
||
onChange={(e) => setGatewayFilters({ ...gatewayFilters, trackId: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 text-xs outline-none focus:ring-1 focus:ring-indigo-500 font-mono"
|
||
dir="ltr"
|
||
/>
|
||
<input
|
||
type="text"
|
||
placeholder="شماره کارت پرداختکننده"
|
||
value={gatewayFilters.cardNumber}
|
||
onChange={(e) => setGatewayFilters({ ...gatewayFilters, cardNumber: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 text-xs outline-none focus:ring-1 focus:ring-indigo-500 font-mono"
|
||
dir="ltr"
|
||
/>
|
||
<input
|
||
type="text"
|
||
placeholder="شماره موبایل"
|
||
value={gatewayFilters.mobile}
|
||
onChange={(e) => setGatewayFilters({ ...gatewayFilters, mobile: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-2.5 text-xs outline-none focus:ring-1 focus:ring-indigo-500 font-mono"
|
||
dir="ltr"
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between pt-2 border-t border-gray-100">
|
||
<button
|
||
onClick={() => setGatewayFilters({ ...gatewayFilters, trackId: '', cardNumber: '', mobile: '' })}
|
||
className="text-indigo-600 hover:text-indigo-800 font-bold text-xs cursor-pointer"
|
||
>
|
||
پاک کردن فرم
|
||
</button>
|
||
<button
|
||
onClick={() => {
|
||
setGatewaySearchOpen(false);
|
||
fetchGatewayTx();
|
||
}}
|
||
className="bg-indigo-600 hover:bg-indigo-700 text-white font-bold px-4 py-2 rounded-xl flex items-center gap-1.5 cursor-pointer"
|
||
>
|
||
<Search className="w-3 h-3" />
|
||
<span>جستجو</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Table */}
|
||
{loadingGatewayTx ? (
|
||
<div className="py-20 flex justify-center">
|
||
<Spinner size="lg" className="text-indigo-600" />
|
||
</div>
|
||
) : gatewayTxList.length === 0 ? (
|
||
<div className="py-16 text-center text-gray-400 space-y-3">
|
||
<div className="w-14 h-14 bg-gray-50 border border-gray-100 rounded-2xl flex items-center justify-center mx-auto text-gray-300">
|
||
<TrendingUp className="w-6 h-6" />
|
||
</div>
|
||
<p className="text-xs font-bold">تراکنشی در درگاه پرداخت اینترنتی یافت نشد.</p>
|
||
</div>
|
||
) : (
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full text-right border-collapse text-xs">
|
||
<thead>
|
||
<tr className="bg-gray-50/80 border-b border-gray-200 text-[11px] font-black text-gray-600">
|
||
<th className="p-3.5">شماره تراکنش (Track ID)</th>
|
||
<th className="p-3.5">شناسه سفارش</th>
|
||
<th className="p-3.5">شماره کارت</th>
|
||
<th className="p-3.5">مبلغ پرداخت</th>
|
||
<th className="p-3.5">شماره موبایل</th>
|
||
<th className="p-3.5">تاریخ پرداخت</th>
|
||
<th className="p-3.5">وضعیت تراکنش</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-gray-100 font-bold">
|
||
{gatewayTxList.map((tx: any, i: number) => (
|
||
<tr key={tx.id || tx.trackId || i} className="hover:bg-gray-50/70 transition-colors">
|
||
<td className="p-3.5 font-mono text-[11px] text-indigo-700">{tx.trackId || '-'}</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-800">{tx.orderId || '-'}</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-700 dir-ltr text-right">{tx.cardNumber || '-'}</td>
|
||
<td className="p-3.5 font-black text-gray-900 font-mono">
|
||
{(Number(tx.amount || 0) / 10).toLocaleString()} تومان
|
||
</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-600 dir-ltr text-right">{tx.mobile || '-'}</td>
|
||
<td className="p-3.5 font-mono text-[11px] text-gray-500">
|
||
{tx.paidAt ? new Date(tx.paidAt).toLocaleDateString('fa-IR') : '-'}
|
||
</td>
|
||
<td className="p-3.5">
|
||
<span
|
||
className={`px-2.5 py-1 rounded-full text-[10px] font-black ${
|
||
tx.status === 1 || tx.status === 2
|
||
? 'bg-emerald-50 text-emerald-700 border border-emerald-200'
|
||
: 'bg-rose-50 text-rose-700 border border-rose-200'
|
||
}`}
|
||
>
|
||
{tx.status === 1 || tx.status === 2 ? 'پرداخت موفق' : 'ناموفق'}
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
{/* ======================================================== */}
|
||
{/* MODAL: NEW REFUND SUBMIT */}
|
||
{/* ======================================================== */}
|
||
{newRefundModal && (
|
||
<Modal
|
||
isOpen={newRefundModal}
|
||
onClose={() => setNewRefundModal(false)}
|
||
title="ثبت و ارسال استرداد وجه جدید (Refund / Reverse)"
|
||
icon={RotateCcw}
|
||
maxWidth="lg"
|
||
footer={
|
||
<div className="flex justify-end gap-2.5 w-full">
|
||
<Button
|
||
variant="secondary"
|
||
size="sm"
|
||
type="button"
|
||
onClick={() => setNewRefundModal(false)}
|
||
>
|
||
انصراف
|
||
</Button>
|
||
<Button
|
||
variant="danger"
|
||
size="sm"
|
||
type="submit"
|
||
form="zibalRefundForm"
|
||
startIcon={Send}
|
||
isLoading={isSubmittingRefund}
|
||
>
|
||
ارسال به زیبال
|
||
</Button>
|
||
</div>
|
||
}
|
||
>
|
||
<div className="space-y-4 text-xs font-vazir text-right">
|
||
<form id="zibalRefundForm" onSubmit={handleCreateRefundSubmit} className="space-y-4 text-xs">
|
||
<div>
|
||
<label className="block font-bold text-gray-700 mb-1">شماره تراکنش شاپرک (Track ID) *</label>
|
||
<input
|
||
type="text"
|
||
required
|
||
placeholder="مثال: 2808993485"
|
||
value={refundForm.trackId}
|
||
onChange={(e) => setRefundForm({ ...refundForm, trackId: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-3 font-mono outline-none focus:ring-2 focus:ring-rose-500"
|
||
dir="ltr"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block font-bold text-gray-700 mb-1">مبلغ استرداد به تومان (اختیاری - خالی برای کل مبلغ)</label>
|
||
<input
|
||
type="number"
|
||
placeholder="کل مبلغ تراکنش"
|
||
value={refundForm.amount}
|
||
onChange={(e) => setRefundForm({ ...refundForm, amount: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-3 font-mono outline-none focus:ring-2 focus:ring-rose-500"
|
||
dir="ltr"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block font-bold text-gray-700 mb-1">شماره کارت مقصد (اختیاری جهت واریز به کارت خاص)</label>
|
||
<input
|
||
type="text"
|
||
placeholder="شماره کارت ۱۶ رقمی"
|
||
value={refundForm.cardNumber}
|
||
onChange={(e) => setRefundForm({ ...refundForm, cardNumber: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-3 font-mono outline-none focus:ring-2 focus:ring-rose-500"
|
||
dir="ltr"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block font-bold text-gray-700 mb-1">توضیحات و علت استرداد</label>
|
||
<textarea
|
||
rows={2}
|
||
placeholder="علت لغو سفارش یا مرجوعی کالا..."
|
||
value={refundForm.description}
|
||
onChange={(e) => setRefundForm({ ...refundForm, description: e.target.value })}
|
||
className="w-full border border-gray-200 rounded-xl p-3 outline-none focus:ring-2 focus:ring-rose-500"
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex items-center gap-2 pt-1">
|
||
<input
|
||
type="checkbox"
|
||
id="modalTryReverse"
|
||
checked={refundForm.tryReverse}
|
||
onChange={(e) => setRefundForm({ ...refundForm, tryReverse: e.target.checked })}
|
||
className="rounded text-rose-600 focus:ring-rose-500 cursor-pointer"
|
||
/>
|
||
<label htmlFor="modalTryReverse" className="font-bold text-gray-700 cursor-pointer">
|
||
اولویت با Reverse باشد (برگشت آنی قبل از تسویه شاپرک)
|
||
</label>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</Modal>
|
||
)}
|
||
|
||
{/* ======================================================== */}
|
||
{/* MODAL: DETAIL / INQUIRY VIEWER */}
|
||
{/* ======================================================== */}
|
||
{detailModalItem && (
|
||
<Modal
|
||
isOpen={!!detailModalItem}
|
||
onClose={() => setDetailModalItem(null)}
|
||
title={detailModalItem.title}
|
||
maxWidth="lg"
|
||
footer={
|
||
<div className="flex justify-end w-full">
|
||
<Button
|
||
variant="secondary"
|
||
size="sm"
|
||
onClick={() => setDetailModalItem(null)}
|
||
>
|
||
بستن
|
||
</Button>
|
||
</div>
|
||
}
|
||
>
|
||
<pre className="bg-slate-900 text-emerald-400 p-4 rounded-xl text-[11px] font-mono overflow-x-auto dir-ltr">
|
||
{JSON.stringify(detailModalItem.data, null, 2)}
|
||
</pre>
|
||
</Modal>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|