From d095f428dc2b62e655c134589ec85f2aebb2e1bb Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 17 Jan 2026 08:48:36 +0300 Subject: [PATCH] Add files via upload --- src/pages/AdminPanel.tsx | 16 ++ src/pages/AdminPayments.tsx | 283 ++++++++++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 src/pages/AdminPayments.tsx diff --git a/src/pages/AdminPanel.tsx b/src/pages/AdminPanel.tsx index e13bb32..a7fad73 100644 --- a/src/pages/AdminPanel.tsx +++ b/src/pages/AdminPanel.tsx @@ -75,6 +75,12 @@ const UsersIcon = ({ className = "w-8 h-8" }: { className?: string }) => ( ) +const PaymentsIcon = ({ className = "w-8 h-8" }: { className?: string }) => ( + + + +) + const ChevronRightIcon = () => ( @@ -244,6 +250,16 @@ export default function AdminPanel() { bgColor: 'bg-indigo-500/20', textColor: 'text-indigo-400' }, + { + to: '/admin/payments', + icon: , + mobileIcon: , + title: t('admin.nav.payments', 'Платежи'), + description: t('admin.panel.paymentsDesc', 'Проверка платежей'), + color: 'lime', + bgColor: 'bg-lime-500/20', + textColor: 'text-lime-400' + }, ] return ( diff --git a/src/pages/AdminPayments.tsx b/src/pages/AdminPayments.tsx new file mode 100644 index 0000000..95aad68 --- /dev/null +++ b/src/pages/AdminPayments.tsx @@ -0,0 +1,283 @@ +import { useState } from 'react' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { useTranslation } from 'react-i18next' +import { Link } from 'react-router-dom' +import { adminPaymentsApi } from '../api/adminPayments' +import { useCurrency } from '../hooks/useCurrency' +import type { PendingPayment, PaginatedResponse } from '../types' + +export default function AdminPayments() { + const { t } = useTranslation() + const queryClient = useQueryClient() + const { formatAmount, currencySymbol } = useCurrency() + + const [page, setPage] = useState(1) + const [methodFilter, setMethodFilter] = useState('') + const [checkingPaymentId, setCheckingPaymentId] = useState(null) + + // Fetch payments + const { data: payments, isLoading, refetch } = useQuery>({ + queryKey: ['admin-payments', page, methodFilter], + queryFn: () => adminPaymentsApi.getPendingPayments({ + page, + per_page: 20, + method_filter: methodFilter || undefined, + }), + refetchInterval: 30000, // Auto-refresh every 30 seconds + }) + + // Fetch stats + const { data: stats } = useQuery({ + queryKey: ['admin-payments-stats'], + queryFn: adminPaymentsApi.getStats, + refetchInterval: 30000, + }) + + // Check payment mutation + const checkPaymentMutation = useMutation({ + mutationFn: ({ method, paymentId }: { method: string; paymentId: number }) => + adminPaymentsApi.checkPaymentStatus(method, paymentId), + onSuccess: async (result) => { + if (result.status_changed) { + await refetch() + queryClient.invalidateQueries({ queryKey: ['admin-payments-stats'] }) + } else { + await refetch() + } + }, + onSettled: () => { + setCheckingPaymentId(null) + }, + }) + + const handleCheckPayment = (payment: PendingPayment) => { + setCheckingPaymentId(`${payment.method}_${payment.id}`) + checkPaymentMutation.mutate({ method: payment.method, paymentId: payment.id }) + } + + // Get unique methods from stats for filter + const methodOptions = stats?.by_method ? Object.keys(stats.by_method) : [] + + return ( +
+ {/* Header */} +
+
+ + + + + +
+

{t('admin.payments.title', 'Проверка платежей')}

+

{t('admin.payments.description', 'Ожидающие платежи за последние 24 часа')}

+
+
+ +
+ + {/* Stats cards */} + {stats && ( +
+
+
{stats.total_pending}
+
{t('admin.payments.totalPending', 'Всего ожидает')}
+
+ {Object.entries(stats.by_method).map(([method, count]) => ( +
setMethodFilter(methodFilter === method ? '' : method)} + > +
{count}
+
{method}
+
+ ))} +
+ )} + + {/* Filter */} + {methodOptions.length > 0 && ( +
+ {t('admin.payments.filterByMethod', 'Фильтр по методу')}: + + {methodOptions.map((method) => ( + + ))} +
+ )} + + {/* Payments list */} +
+ {isLoading ? ( +
+
+
+ ) : payments?.items && payments.items.length > 0 ? ( +
+ {payments.items.map((payment) => { + const paymentKey = `${payment.method}_${payment.id}` + const isChecking = checkingPaymentId === paymentKey + + return ( +
+
+
+
+ {payment.method_display} + + {payment.status_emoji} {payment.status_text} + + {payment.is_paid && ( + + {t('admin.payments.paid', 'Оплачено')} + + )} +
+
+ {formatAmount(payment.amount_rubles)} {currencySymbol} +
+
+ ID: {payment.identifier} +
+
+ {new Date(payment.created_at).toLocaleString()} +
+ {/* User info */} + {(payment.user_username || payment.user_telegram_id) && ( +
+ {t('admin.payments.user', 'Пользователь')}:{' '} + {payment.user_username ? ( + @{payment.user_username} + ) : ( + ID: {payment.user_telegram_id} + )} +
+ )} +
+
+ {payment.payment_url && ( + + {t('admin.payments.openLink', 'Открыть ссылку')} + + )} + {payment.is_checkable && ( + + )} +
+
+ {/* Show result after check */} + {checkPaymentMutation.isSuccess && checkPaymentMutation.variables?.paymentId === payment.id && ( +
+ {checkPaymentMutation.data?.message} +
+ )} +
+ ) + })} +
+ ) : ( +
+
+ + + +
+
{t('admin.payments.noPayments', 'Нет ожидающих платежей')}
+
+ )} + + {/* Pagination */} + {payments && payments.pages > 1 && ( +
+ +
+ {t('balance.page', '{current} / {total}', { current: payments.page, total: payments.pages })} +
+ +
+ )} +
+
+ ) +}