526 lines
22 KiB
TypeScript
526 lines
22 KiB
TypeScript
import React, { useState } from 'react';
|
||
import { usePaymentMethodsReport } from '../core/_hooks';
|
||
import { PaymentMethodsFilters } from '../core/_models';
|
||
import { Button } from '@/components/ui/Button';
|
||
import { Input } from '@/components/ui/Input';
|
||
import { Table } from '@/components/ui/Table';
|
||
import { TableColumn } from '@/types';
|
||
import { JalaliDateTimePicker } from '@/components/ui/JalaliDateTimePicker';
|
||
import { PageContainer, PageTitle } from '@/components/ui/Typography';
|
||
import { Pagination } from '@/components/ui/Pagination';
|
||
import { Filter, TrendingUp, Users, DollarSign, CreditCard, CheckCircle, XCircle, X } from 'lucide-react';
|
||
import { formatWithThousands, persianToEnglish } from '@/utils/numberUtils';
|
||
import { PieChart } from '@/components/charts/PieChart';
|
||
|
||
const formatCurrency = (amount: number) => {
|
||
return formatWithThousands(amount) + ' تومان';
|
||
};
|
||
|
||
const formatDate = (dateString: string) => {
|
||
if (!dateString) return '-';
|
||
return new Date(dateString).toLocaleDateString('fa-IR', {
|
||
year: 'numeric',
|
||
month: 'long',
|
||
day: 'numeric',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
});
|
||
};
|
||
|
||
const formatPercentage = (value: number) => {
|
||
return formatWithThousands(value.toFixed(2)) + '%';
|
||
};
|
||
|
||
const getPaymentTypeLabel = (type: string): string => {
|
||
const labels: Record<string, string> = {
|
||
'bank-topup': 'افزایش موجودی کیف پول',
|
||
'card-to-card': 'پرداخت به روش کارت به کارت',
|
||
'debit-rial-wallet': 'پرداخت از کیف ریالی',
|
||
'debit-gold18k-wallet': 'پرداخت از کیف طلا',
|
||
};
|
||
return labels[type] || type;
|
||
};
|
||
|
||
const PaymentMethodsReportSkeleton = () => (
|
||
<>
|
||
{/* Summary Cards Skeleton */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||
{[...Array(4)].map((_, i) => (
|
||
<div key={i} className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-4 animate-pulse">
|
||
<div className="flex items-center gap-3">
|
||
<div className="p-2 bg-gray-200 dark:bg-gray-700 rounded-lg w-12 h-12"></div>
|
||
<div className="flex-1">
|
||
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-20 mb-2"></div>
|
||
<div className="h-6 bg-gray-200 dark:bg-gray-700 rounded w-16"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Pie Chart and Total Amount Skeleton */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||
<div className="lg:col-span-2 bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-6 animate-pulse">
|
||
<div className="h-6 bg-gray-200 dark:bg-gray-700 rounded w-48 mb-6"></div>
|
||
<div className="h-64 bg-gray-200 dark:bg-gray-700 rounded"></div>
|
||
</div>
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-6 animate-pulse">
|
||
<div className="h-16 w-16 bg-gray-200 dark:bg-gray-700 rounded-full mx-auto mb-4"></div>
|
||
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-24 mx-auto mb-2"></div>
|
||
<div className="h-8 bg-gray-200 dark:bg-gray-700 rounded w-40 mx-auto"></div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Payment Type Cards Skeleton */}
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-6 mb-6 animate-pulse">
|
||
<div className="h-6 bg-gray-200 dark:bg-gray-700 rounded w-48 mb-6"></div>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4">
|
||
{[...Array(5)].map((_, i) => (
|
||
<div key={i} className="border-2 border-gray-200 dark:border-gray-700 rounded-lg p-5 bg-gray-50 dark:bg-gray-700/50">
|
||
<div className="h-5 bg-gray-200 dark:bg-gray-600 rounded w-32 mb-4"></div>
|
||
<div className="space-y-2.5">
|
||
{[...Array(5)].map((_, j) => (
|
||
<div key={j} className="flex justify-between">
|
||
<div className="h-4 bg-gray-200 dark:bg-gray-600 rounded w-16"></div>
|
||
<div className="h-4 bg-gray-200 dark:bg-gray-600 rounded w-12"></div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Table Skeleton */}
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
|
||
<div className="overflow-x-auto">
|
||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||
<thead className="bg-gray-50 dark:bg-gray-700">
|
||
<tr>
|
||
{[...Array(10)].map((_, i) => (
|
||
<th key={i} className="px-6 py-3 text-right text-xs font-medium text-gray-500 dark:text-gray-300 uppercase tracking-wider">
|
||
<div className="h-4 bg-gray-200 dark:bg-gray-600 rounded w-20"></div>
|
||
</th>
|
||
))}
|
||
</tr>
|
||
</thead>
|
||
<tbody className="bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700">
|
||
{[...Array(5)].map((_, i) => (
|
||
<tr key={i} className="animate-pulse">
|
||
{[...Array(10)].map((_, j) => (
|
||
<td key={j} className="px-6 py-4 whitespace-nowrap">
|
||
<div className="h-4 bg-gray-200 dark:bg-gray-600 rounded"></div>
|
||
</td>
|
||
))}
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</>
|
||
);
|
||
|
||
const PaymentMethodsReportPage = () => {
|
||
const [filters, setFilters] = useState<PaymentMethodsFilters>({
|
||
limit: 50,
|
||
offset: 0,
|
||
group_by_user: false,
|
||
});
|
||
|
||
const { data, isLoading, error } = usePaymentMethodsReport(filters);
|
||
|
||
const handleFilterChange = (key: keyof PaymentMethodsFilters, value: any) => {
|
||
setFilters(prev => ({
|
||
...prev,
|
||
[key]: value,
|
||
offset: 0,
|
||
}));
|
||
};
|
||
|
||
const handleDateRangeChange = (from: string | undefined, to: string | undefined) => {
|
||
setFilters(prev => ({
|
||
...prev,
|
||
date_range: {
|
||
from,
|
||
to,
|
||
},
|
||
offset: 0,
|
||
}));
|
||
};
|
||
|
||
const handleNumericFilterChange = (key: 'user_id', raw: string) => {
|
||
const converted = persianToEnglish(raw).replace(/[^\d]/g, '');
|
||
const numeric = converted ? Number(converted) : undefined;
|
||
handleFilterChange(key, numeric);
|
||
};
|
||
|
||
const handlePageChange = (page: number) => {
|
||
setFilters(prev => ({
|
||
...prev,
|
||
offset: (page - 1) * prev.limit,
|
||
}));
|
||
};
|
||
|
||
const handleClearFilters = () => {
|
||
setFilters({
|
||
limit: 50,
|
||
offset: 0,
|
||
group_by_user: false,
|
||
});
|
||
};
|
||
|
||
const columns: TableColumn[] = [
|
||
{
|
||
key: 'customer_name',
|
||
label: 'نام مشتری',
|
||
align: 'right',
|
||
},
|
||
{
|
||
key: 'customer_phone',
|
||
label: 'شماره تماس',
|
||
align: 'right',
|
||
},
|
||
{
|
||
key: 'payment_type',
|
||
label: 'نوع پرداخت',
|
||
align: 'right',
|
||
},
|
||
{
|
||
key: 'successful_count',
|
||
label: 'موفق',
|
||
align: 'right',
|
||
},
|
||
{
|
||
key: 'failed_count',
|
||
label: 'ناموفق',
|
||
align: 'right',
|
||
},
|
||
{
|
||
key: 'total_attempts',
|
||
label: 'کل تلاشها',
|
||
align: 'right',
|
||
},
|
||
{
|
||
key: 'total_amount',
|
||
label: 'مجموع مبلغ',
|
||
align: 'right',
|
||
},
|
||
{
|
||
key: 'success_rate',
|
||
label: 'نرخ موفقیت',
|
||
align: 'right',
|
||
},
|
||
{
|
||
key: 'first_used_at',
|
||
label: 'اولین استفاده',
|
||
align: 'right',
|
||
},
|
||
{
|
||
key: 'last_used_at',
|
||
label: 'آخرین استفاده',
|
||
align: 'right',
|
||
},
|
||
];
|
||
|
||
const tableData = (data?.payment_methods || []).map(method => ({
|
||
customer_name: method.customer_name || '-',
|
||
customer_phone: method.customer_phone || '-',
|
||
payment_type: getPaymentTypeLabel(method.payment_type),
|
||
successful_count: formatWithThousands(method.successful_count),
|
||
failed_count: formatWithThousands(method.failed_count),
|
||
total_attempts: formatWithThousands(method.total_attempts),
|
||
total_amount: formatCurrency(method.total_amount),
|
||
success_rate: formatPercentage(method.success_rate),
|
||
first_used_at: formatDate(method.first_used_at),
|
||
last_used_at: formatDate(method.last_used_at),
|
||
})) || [];
|
||
|
||
const currentPage = Math.floor(filters.offset / filters.limit) + 1;
|
||
const totalPages = data ? Math.ceil(data.total / filters.limit) : 1;
|
||
|
||
return (
|
||
<PageContainer>
|
||
<PageTitle>گزارش روشهای پرداخت</PageTitle>
|
||
|
||
{/* Filters */}
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-4 mb-6">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<div className="flex items-center gap-2">
|
||
<Filter className="h-5 w-5 text-gray-500" />
|
||
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">فیلترها</h3>
|
||
</div>
|
||
<Button
|
||
variant="secondary"
|
||
size="sm"
|
||
onClick={handleClearFilters}
|
||
className="flex items-center gap-2"
|
||
>
|
||
<X className="h-4 w-4" />
|
||
پاک کردن فیلترها
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
شناسه کاربر
|
||
</label>
|
||
<Input
|
||
value={filters.user_id?.toString() || ''}
|
||
onChange={(e) => handleNumericFilterChange('user_id', e.target.value)}
|
||
placeholder="مثلاً 456"
|
||
numeric
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
نوع پرداخت
|
||
</label>
|
||
<select
|
||
value={filters.payment_type || ''}
|
||
onChange={(e) => handleFilterChange('payment_type', e.target.value || undefined)}
|
||
className="w-full px-3 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-gray-100 transition-all duration-200"
|
||
>
|
||
<option value="">همه</option>
|
||
<option value="bank-topup">افزایش موجودی کیف پول</option>
|
||
<option value="card-to-card">پرداخت به روش کارت به کارت</option>
|
||
<option value="debit-rial-wallet">پرداخت از کیف ریالی</option>
|
||
<option value="debit-gold18k-wallet">پرداخت از کیف طلا</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
وضعیت
|
||
</label>
|
||
<select
|
||
value={filters.status || ''}
|
||
onChange={(e) => handleFilterChange('status', e.target.value || undefined)}
|
||
className="w-full px-3 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-gray-100 transition-all duration-200"
|
||
>
|
||
<option value="">همه</option>
|
||
<option value="pending">در انتظار</option>
|
||
<option value="paid">پرداخت شده</option>
|
||
<option value="failed">ناموفق</option>
|
||
<option value="refunded">مرجوع شده</option>
|
||
<option value="cancelled">لغو شده</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
تاریخ شروع
|
||
</label>
|
||
<JalaliDateTimePicker
|
||
value={filters.date_range?.from}
|
||
onChange={(value) => handleDateRangeChange(value, filters.date_range?.to)}
|
||
placeholder="انتخاب تاریخ شروع"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
تاریخ پایان
|
||
</label>
|
||
<JalaliDateTimePicker
|
||
value={filters.date_range?.to}
|
||
onChange={(value) => handleDateRangeChange(filters.date_range?.from, value)}
|
||
placeholder="انتخاب تاریخ پایان"
|
||
/>
|
||
</div>
|
||
|
||
<div className="flex items-end">
|
||
<label className="inline-flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||
<input
|
||
type="checkbox"
|
||
checked={filters.group_by_user || false}
|
||
onChange={(e) => handleFilterChange('group_by_user', e.target.checked)}
|
||
className="rounded border-gray-300 dark:border-gray-600"
|
||
/>
|
||
گروهبندی بر اساس کاربر
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Summary Cards */}
|
||
{data?.summary && (
|
||
<>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className="p-2 bg-blue-100 dark:bg-blue-900 rounded-lg">
|
||
<CreditCard className="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-gray-600 dark:text-gray-400">کل تراکنشها</p>
|
||
<p className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
||
{formatWithThousands(data.summary.total_transactions)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className="p-2 bg-green-100 dark:bg-green-900 rounded-lg">
|
||
<CheckCircle className="h-5 w-5 text-green-600 dark:text-green-400" />
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-gray-600 dark:text-gray-400">تراکنشهای موفق</p>
|
||
<p className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
||
{formatWithThousands(data.summary.successful_transactions)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className="p-2 bg-red-100 dark:bg-red-900 rounded-lg">
|
||
<XCircle className="h-5 w-5 text-red-600 dark:text-red-400" />
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-gray-600 dark:text-gray-400">تراکنشهای ناموفق</p>
|
||
<p className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
||
{formatWithThousands(data.summary.failed_transactions)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-4">
|
||
<div className="flex items-center gap-3">
|
||
<div className="p-2 bg-purple-100 dark:bg-purple-900 rounded-lg">
|
||
<TrendingUp className="h-5 w-5 text-purple-600 dark:text-purple-400" />
|
||
</div>
|
||
<div>
|
||
<p className="text-sm text-gray-600 dark:text-gray-400">نرخ موفقیت کلی</p>
|
||
<p className="text-xl font-bold text-gray-900 dark:text-gray-100">
|
||
{formatPercentage(data.summary.overall_success_rate)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Payment Type Breakdown */}
|
||
{Object.keys(data.summary.by_payment_type).length > 0 && (
|
||
<>
|
||
{/* Pie Chart and Total Amount */}
|
||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||
{/* Pie Chart */}
|
||
<div className="lg:col-span-2 bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-6">
|
||
<h3 className="text-xl font-bold text-gray-900 dark:text-gray-100 mb-6">
|
||
نمودار توزیع روشهای پرداخت
|
||
</h3>
|
||
<PieChart
|
||
data={Object.entries(data.summary.by_payment_type).map(([type, stats]) => ({
|
||
name: getPaymentTypeLabel(type),
|
||
value: stats.percentage,
|
||
}))}
|
||
title="درصد استفاده از هر روش پرداخت"
|
||
colors={['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6', '#ec4899', '#06b6d4', '#14b8a6', '#f97316']}
|
||
/>
|
||
</div>
|
||
|
||
{/* Total Amount Card */}
|
||
<div className="bg-gradient-to-br from-yellow-50 to-yellow-100 dark:from-yellow-900/20 dark:to-yellow-800/20 shadow-sm border-2 border-yellow-200 dark:border-yellow-800 rounded-lg p-6 flex flex-col justify-center">
|
||
<div className="text-center">
|
||
<div className="inline-flex items-center justify-center w-16 h-16 bg-yellow-500 dark:bg-yellow-600 rounded-full mb-4 shadow-lg">
|
||
<DollarSign className="h-8 w-8 text-white" />
|
||
</div>
|
||
<p className="text-sm font-medium text-gray-600 dark:text-gray-400 mb-2">مجموع مبلغ</p>
|
||
<p className="text-3xl font-bold text-gray-900 dark:text-gray-100">
|
||
{formatCurrency(data.summary.total_amount)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Payment Type Cards */}
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg p-6 mb-6">
|
||
<h3 className="text-xl font-bold text-gray-900 dark:text-gray-100 mb-6">
|
||
آمار تفکیکی هر روش پرداخت
|
||
</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4">
|
||
{Object.entries(data.summary.by_payment_type).map(([type, stats]) => (
|
||
<div
|
||
key={type}
|
||
className="border-2 border-gray-200 dark:border-gray-700 rounded-lg p-5 bg-gray-50 dark:bg-gray-700/50 hover:shadow-md transition-shadow"
|
||
>
|
||
<h4 className="font-bold text-base text-gray-900 dark:text-gray-100 mb-4 pb-2 border-b border-gray-200 dark:border-gray-600">{getPaymentTypeLabel(type)}</h4>
|
||
<div className="space-y-2.5 text-base">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-700 dark:text-gray-300 font-medium">کل:</span>
|
||
<span className="font-bold text-gray-900 dark:text-gray-100">{formatWithThousands(stats.count)}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-700 dark:text-gray-300 font-medium">موفق:</span>
|
||
<span className="font-bold text-green-600 dark:text-green-400">{formatWithThousands(stats.success_count)}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-700 dark:text-gray-300 font-medium">ناموفق:</span>
|
||
<span className="font-bold text-red-600 dark:text-red-400">{formatWithThousands(stats.failed_count)}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-gray-700 dark:text-gray-300 font-medium">نرخ موفقیت:</span>
|
||
<span className="font-bold text-blue-600 dark:text-blue-400">{formatPercentage(stats.success_rate)}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center pt-2 border-t border-gray-200 dark:border-gray-600">
|
||
<span className="text-gray-700 dark:text-gray-300 font-medium">درصد از کل:</span>
|
||
<span className="font-bold text-purple-600 dark:text-purple-400">{formatPercentage(stats.percentage)}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</>
|
||
)}
|
||
|
||
{/* Table */}
|
||
{isLoading ? (
|
||
<PaymentMethodsReportSkeleton />
|
||
) : error ? (
|
||
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
|
||
<p className="text-red-600 dark:text-red-400">خطا در دریافت دادهها</p>
|
||
</div>
|
||
) : (
|
||
<>
|
||
<div className="bg-white dark:bg-gray-800 shadow-sm border border-gray-200 dark:border-gray-700 rounded-lg">
|
||
<div className="overflow-x-auto">
|
||
<Table columns={columns} data={tableData} loading={isLoading} />
|
||
</div>
|
||
</div>
|
||
|
||
{data && data.total > 0 && totalPages > 1 && (
|
||
<div className="mt-4 flex justify-center">
|
||
<Pagination
|
||
currentPage={currentPage}
|
||
totalPages={totalPages}
|
||
onPageChange={handlePageChange}
|
||
itemsPerPage={filters.limit}
|
||
totalItems={data.total}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{data && data.total === 0 && (
|
||
<div className="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-8 text-center">
|
||
<p className="text-gray-600 dark:text-gray-400">دادهای یافت نشد</p>
|
||
</div>
|
||
)}
|
||
</>
|
||
)}
|
||
</PageContainer>
|
||
);
|
||
};
|
||
|
||
export default PaymentMethodsReportPage;
|
||
|