From b2b80849ea99eae2f0dd20fbe703bd2292cb5191 Mon Sep 17 00:00:00 2001 From: parsa aghaei Date: Sun, 23 Aug 2026 10:55:09 +0330 Subject: [PATCH] fix: resolve variable declaration order, modal state and lint errors --- backend/src/admin/admin.controller.ts | 8 ++ backend/src/admin/admin.service.ts | 23 ++++ frontend/admin-panel/src/pages/Blogs.tsx | 57 ++++---- frontend/admin-panel/src/pages/Categories.tsx | 55 ++++---- frontend/admin-panel/src/pages/Coupons.tsx | 59 +++++---- .../admin-panel/src/pages/MenuManager.tsx | 55 ++++---- frontend/admin-panel/src/pages/Orders.tsx | 101 ++++++++------ frontend/admin-panel/src/pages/Products.tsx | 125 +++++++++--------- .../admin-panel/src/pages/Transactions.tsx | 29 ++-- frontend/admin-panel/src/pages/Users.tsx | 86 ++++++------ frontend/admin-panel/src/pages/Wiki.tsx | 57 ++++---- 11 files changed, 353 insertions(+), 302 deletions(-) diff --git a/backend/src/admin/admin.controller.ts b/backend/src/admin/admin.controller.ts index 4575239..bc6902b 100644 --- a/backend/src/admin/admin.controller.ts +++ b/backend/src/admin/admin.controller.ts @@ -157,12 +157,20 @@ export class AdminController { return { success: true, ...result }; } + @Get('orders/:id') + @ApiOperation({ summary: 'دریافت جزئیات کامل یک سفارش با شناسه' }) + async getOrderById(@Param('id') id: string) { + const order = await this.adminService.getOrderById(id); + return { success: true, data: order }; + } + @Put('orders/:id/status') @ApiOperation({ summary: 'بروزرسانی وضعیت سفارش' }) async updateOrderStatus( @Param('id') id: string, @Body('status') status: string, @Body('trackingCode') trackingCode?: string, + ) { const order = await this.adminService.updateOrderStatus( id, diff --git a/backend/src/admin/admin.service.ts b/backend/src/admin/admin.service.ts index 30a06dc..8b6d211 100644 --- a/backend/src/admin/admin.service.ts +++ b/backend/src/admin/admin.service.ts @@ -596,8 +596,31 @@ export class AdminService { }; } + async getOrderById(id: string) { + const order = await this.prisma.order.findUnique({ + where: { id }, + include: { + user: true, + orderItems: { + include: { + product: true, + }, + }, + coupon: true, + paymentTransactions: { + orderBy: { createdAt: 'desc' }, + }, + }, + }); + if (!order) { + throw new NotFoundException(`سفارش با شناسه ${id} یافت نشد`); + } + return order; + } + async updateOrderStatus(id: string, status: string, trackingNumber?: string) { const dataToUpdate: Prisma.OrderUpdateInput = { status }; + if (trackingNumber !== undefined) { dataToUpdate.trackingNumber = trackingNumber; } diff --git a/frontend/admin-panel/src/pages/Blogs.tsx b/frontend/admin-panel/src/pages/Blogs.tsx index 74faaf6..b6b0cd4 100644 --- a/frontend/admin-panel/src/pages/Blogs.tsx +++ b/frontend/admin-panel/src/pages/Blogs.tsx @@ -65,6 +65,35 @@ export default function Blogs() { imageUrl: '' }); + const openModal = (blog: BlogPost | null = null) => { + if (blog) { + setEditingBlog(blog); + updateUrlParams({ modal: 'edit', blogId: blog.id }); + setFormData({ + title: blog.title, + slug: blog.slug, + content: blog.content, + isPublished: blog.isPublished, + metaTitle: blog.metaTitle || '', + metaDescription: blog.metaDescription || '', + keywords: blog.keywords || '', + imageUrl: blog.imageUrl || '' + }); + } else { + setEditingBlog(null); + updateUrlParams({ modal: 'create', blogId: undefined }); + setFormData({ + title: '', slug: '', content: '', isPublished: true, metaTitle: '', metaDescription: '', keywords: '', imageUrl: '' + }); + } + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + updateUrlParams({ modal: undefined, blogId: undefined }); + }; + const fetchBlogs = useCallback(async () => { try { setIsLoading(true); @@ -129,34 +158,6 @@ export default function Blogs() { } }; - const openModal = (blog: BlogPost | null = null) => { - if (blog) { - setEditingBlog(blog); - updateUrlParams({ modal: 'edit', blogId: blog.id }); - setFormData({ - title: blog.title, - slug: blog.slug, - content: blog.content, - isPublished: blog.isPublished, - metaTitle: blog.metaTitle || '', - metaDescription: blog.metaDescription || '', - keywords: blog.keywords || '', - imageUrl: blog.imageUrl || '' - }); - } else { - setEditingBlog(null); - updateUrlParams({ modal: 'create', blogId: undefined }); - setFormData({ - title: '', slug: '', content: '', isPublished: true, metaTitle: '', metaDescription: '', keywords: '', imageUrl: '' - }); - } - setIsModalOpen(true); - }; - - const closeModal = () => { - setIsModalOpen(false); - updateUrlParams({ modal: undefined, blogId: undefined }); - }; return ( diff --git a/frontend/admin-panel/src/pages/Categories.tsx b/frontend/admin-panel/src/pages/Categories.tsx index fc77941..5dc16aa 100644 --- a/frontend/admin-panel/src/pages/Categories.tsx +++ b/frontend/admin-panel/src/pages/Categories.tsx @@ -59,6 +59,34 @@ export default function Categories() { imageUrl: '' }); + const openModal = (category: Category | null = null) => { + if (category) { + setEditingCategory(category); + updateUrlParams({ modal: 'edit', categoryId: category.id }); + setFormData({ + name: category.name, + slug: category.slug, + description: category.description || '', + metaTitle: category.metaTitle || '', + metaDescription: category.metaDescription || '', + keywords: category.keywords || '', + imageUrl: category.imageUrl || '' + }); + } else { + setEditingCategory(null); + updateUrlParams({ modal: 'create', categoryId: undefined }); + setFormData({ + name: '', slug: '', description: '', metaTitle: '', metaDescription: '', keywords: '', imageUrl: '' + }); + } + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + updateUrlParams({ modal: undefined, categoryId: undefined }); + }; + const fetchCategories = useCallback(async () => { try { setIsLoading(true); @@ -123,33 +151,6 @@ export default function Categories() { } }; - const openModal = (category: Category | null = null) => { - if (category) { - setEditingCategory(category); - updateUrlParams({ modal: 'edit', categoryId: category.id }); - setFormData({ - name: category.name, - slug: category.slug, - description: category.description || '', - metaTitle: category.metaTitle || '', - metaDescription: category.metaDescription || '', - keywords: category.keywords || '', - imageUrl: category.imageUrl || '' - }); - } else { - setEditingCategory(null); - updateUrlParams({ modal: 'create', categoryId: undefined }); - setFormData({ - name: '', slug: '', description: '', metaTitle: '', metaDescription: '', keywords: '', imageUrl: '' - }); - } - setIsModalOpen(true); - }; - - const closeModal = () => { - setIsModalOpen(false); - updateUrlParams({ modal: undefined, categoryId: undefined }); - }; return ( diff --git a/frontend/admin-panel/src/pages/Coupons.tsx b/frontend/admin-panel/src/pages/Coupons.tsx index 4bad5d0..930f216 100644 --- a/frontend/admin-panel/src/pages/Coupons.tsx +++ b/frontend/admin-panel/src/pages/Coupons.tsx @@ -72,6 +72,36 @@ export default function Coupons() { setSearchParams(cleaned, { replace: true }); }; + const openModal = (coupon: Coupon | null = null) => { + if (coupon) { + setEditingCoupon(coupon); + updateUrlParams({ modal: 'edit', couponId: coupon.id }); + setFormData({ + code: coupon.code, + type: coupon.type, + value: Number(coupon.value), + minCartValue: coupon.minCartValue ? String(coupon.minCartValue) : '', + maxCartValue: coupon.maxCartValue ? String(coupon.maxCartValue) : '', + maxUses: coupon.maxUses ? String(coupon.maxUses) : '', + expiresAt: coupon.expiresAt ? new Date(coupon.expiresAt).toISOString().split('T')[0] : '', + isActive: coupon.isActive, + targets: coupon.targets || [] + }); + } else { + setEditingCoupon(null); + updateUrlParams({ modal: 'create', couponId: undefined }); + setFormData({ + code: '', type: 'percent', value: 0, minCartValue: '', maxCartValue: '', maxUses: '', expiresAt: '', isActive: true, targets: [] + }); + } + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + updateUrlParams({ modal: undefined, couponId: undefined }); + }; + const fetchData = useCallback(async () => { try { setIsLoading(true); @@ -102,35 +132,6 @@ export default function Coupons() { return () => clearTimeout(timer); }, [fetchData]); - const openModal = (coupon: Coupon | null = null) => { - if (coupon) { - setEditingCoupon(coupon); - updateUrlParams({ modal: 'edit', couponId: coupon.id }); - setFormData({ - code: coupon.code, - type: coupon.type, - value: Number(coupon.value), - minCartValue: coupon.minCartValue ? String(coupon.minCartValue) : '', - maxCartValue: coupon.maxCartValue ? String(coupon.maxCartValue) : '', - maxUses: coupon.maxUses ? String(coupon.maxUses) : '', - expiresAt: coupon.expiresAt ? new Date(coupon.expiresAt).toISOString().split('T')[0] : '', - isActive: coupon.isActive, - targets: coupon.targets || [] - }); - } else { - setEditingCoupon(null); - updateUrlParams({ modal: 'create', couponId: undefined }); - setFormData({ - code: '', type: 'percent', value: 0, minCartValue: '', maxCartValue: '', maxUses: '', expiresAt: '', isActive: true, targets: [] - }); - } - setIsModalOpen(true); - }; - - const closeModal = () => { - setIsModalOpen(false); - updateUrlParams({ modal: undefined, couponId: undefined }); - }; const handleSave = async (e: React.FormEvent) => { diff --git a/frontend/admin-panel/src/pages/MenuManager.tsx b/frontend/admin-panel/src/pages/MenuManager.tsx index 1f1a9a0..06f8e62 100644 --- a/frontend/admin-panel/src/pages/MenuManager.tsx +++ b/frontend/admin-panel/src/pages/MenuManager.tsx @@ -75,33 +75,6 @@ export default function MenuManager() { badge: '', }); - const fetchAll = useCallback(async () => { - try { - setIsLoading(true); - const res = await api.get('/menu/admin'); - const data: MenuItem[] = Array.isArray(res.data) ? res.data : (res.data?.data || []); - setAllItems(data); - - const modalParam = searchParams.get('modal'); - const itemIdParam = searchParams.get('itemId'); - if (modalParam === 'create') { - openModal(null); - } else if (modalParam === 'edit' && itemIdParam) { - const found = data.find((it) => it.id === itemIdParam); - if (found) openModal(found); - } - } catch (err) { - console.error('Failed to fetch menu items:', err); - toast.error('خطا در دریافت آیتم‌های منو'); - } finally { - setIsLoading(false); - } - }, [searchParams]); - - useEffect(() => { - fetchAll(); - }, [fetchAll]); - const currentItems = allItems.filter( (item) => item.menuType === activeTab && item.parentId === null ); @@ -144,6 +117,34 @@ export default function MenuManager() { updateUrlState({ modal: null, itemId: null }); }; + const fetchAll = useCallback(async () => { + try { + setIsLoading(true); + const res = await api.get('/menu/admin'); + const data: MenuItem[] = Array.isArray(res.data) ? res.data : (res.data?.data || []); + setAllItems(data); + + const modalParam = searchParams.get('modal'); + const itemIdParam = searchParams.get('itemId'); + if (modalParam === 'create') { + openModal(null); + } else if (modalParam === 'edit' && itemIdParam) { + const found = data.find((it) => it.id === itemIdParam); + if (found) openModal(found); + } + } catch (err) { + console.error('Failed to fetch menu items:', err); + toast.error('خطا در دریافت آیتم‌های منو'); + } finally { + setIsLoading(false); + } + }, [searchParams]); + + useEffect(() => { + fetchAll(); + }, [fetchAll]); + + const handleSave = async (e: React.FormEvent) => { e.preventDefault(); diff --git a/frontend/admin-panel/src/pages/Orders.tsx b/frontend/admin-panel/src/pages/Orders.tsx index de129d8..4785917 100644 --- a/frontend/admin-panel/src/pages/Orders.tsx +++ b/frontend/admin-panel/src/pages/Orders.tsx @@ -212,6 +212,40 @@ export default function Orders() { const [modalStatus, setModalStatus] = useState(''); const [isSavingTracking, setIsSavingTracking] = useState(false); + // Refund Order State & Handlers + const [refundModalOrder, setRefundModalOrder] = useState(null); + const [refundTarget, setRefundTarget] = useState<'wallet' | 'zibal'>('wallet'); + const [refundAmount, setRefundAmount] = useState(''); + const [refundReason, setRefundReason] = useState(''); + const [isProcessingRefund, setIsProcessingRefund] = useState(false); + + const openOrderModal = (order: Order) => { + setSelectedOrder(order); + setModalTrackingCode(order.trackingNumber || ''); + setModalStatus(order.status || 'processing'); + updateUrlParams({ modal: 'details', orderId: order.id }); + }; + + const closeOrderModal = () => { + setSelectedOrder(null); + updateUrlParams({ modal: undefined, orderId: undefined }); + }; + + const openOrderRefundModal = (order: Order) => { + setRefundModalOrder(order); + const amt = Number(order.totalAmount || order.total || 0); + setRefundAmount(String(amt)); + // If order was paid by wallet, default to wallet. If online, let admin choose. + setRefundTarget(order.paymentMethod === 'wallet' ? 'wallet' : 'zibal'); + setRefundReason(`استرداد سفارش #${order.trackingNumber || order.id.slice(0, 8)}`); + updateUrlParams({ modal: 'refund', orderId: order.id }); + }; + + const closeOrderRefundModal = () => { + setRefundModalOrder(null); + updateUrlParams({ modal: undefined, orderId: undefined }); + }; + const fetchOrders = useCallback(async () => { try { setIsLoading(true); @@ -238,7 +272,18 @@ export default function Orders() { const modalType = searchParams.get('modal'); const orderIdParam = searchParams.get('orderId'); if (modalType && orderIdParam) { - const foundOrder = orderList.find((o: Order) => o.id === orderIdParam); + let foundOrder = orderList.find((o: Order) => o.id === orderIdParam); + if (!foundOrder) { + try { + const singleRes = await api.get(`/admin/orders/${orderIdParam}`); + if (singleRes.data?.data) { + foundOrder = singleRes.data.data; + } + } catch (err) { + console.warn('Could not fetch single order by ID', err); + } + } + if (foundOrder) { if (modalType === 'details') { setSelectedOrder(foundOrder); @@ -284,18 +329,6 @@ export default function Orders() { } }; - const openOrderModal = (order: Order) => { - setSelectedOrder(order); - setModalTrackingCode(order.trackingNumber || ''); - setModalStatus(order.status || 'processing'); - updateUrlParams({ modal: 'details', orderId: order.id }); - }; - - const closeOrderModal = () => { - setSelectedOrder(null); - updateUrlParams({ modal: undefined, orderId: undefined }); - }; - const handleSaveModalChanges = async () => { if (!selectedOrder) return; try { @@ -314,27 +347,6 @@ export default function Orders() { } }; - // Refund Order State & Handlers - const [refundModalOrder, setRefundModalOrder] = useState(null); - const [refundTarget, setRefundTarget] = useState<'wallet' | 'zibal'>('wallet'); - const [refundAmount, setRefundAmount] = useState(''); - const [refundReason, setRefundReason] = useState(''); - const [isProcessingRefund, setIsProcessingRefund] = useState(false); - - const openOrderRefundModal = (order: Order) => { - setRefundModalOrder(order); - const amt = Number(order.totalAmount || order.total || 0); - setRefundAmount(String(amt)); - // If order was paid by wallet, default to wallet. If online, let admin choose. - setRefundTarget(order.paymentMethod === 'wallet' ? 'wallet' : 'zibal'); - setRefundReason(`استرداد سفارش #${order.trackingNumber || order.id.slice(0, 8)}`); - updateUrlParams({ modal: 'refund', orderId: order.id }); - }; - - const closeOrderRefundModal = () => { - setRefundModalOrder(null); - updateUrlParams({ modal: undefined, orderId: undefined }); - }; const handleExecuteOrderRefund = async () => { @@ -848,15 +860,20 @@ export default function Orders() {
زمان ثبت سفارش:
- {toPersianDigits(new Date(selectedOrder.createdAt).toLocaleDateString('fa-IR', { - weekday: 'long', - year: 'numeric', - month: 'long', - day: 'numeric', - hour: '2-digit', - minute: '2-digit', - }))} + {selectedOrder.createdAt + ? toPersianDigits( + new Date(selectedOrder.createdAt).toLocaleDateString('fa-IR', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }) + ) + : '-'}
+
diff --git a/frontend/admin-panel/src/pages/Products.tsx b/frontend/admin-panel/src/pages/Products.tsx index 04c570a..0b92ed4 100644 --- a/frontend/admin-panel/src/pages/Products.tsx +++ b/frontend/admin-panel/src/pages/Products.tsx @@ -195,68 +195,6 @@ export default function Products() { preorderDeposit: '' as number | string }); - const fetchData = useCallback(async () => { - try { - setIsLoading(true); - // Fetch products with full query params - const prodRes = await api.get('/admin/products', { - params: { - page, - limit, - search: search || undefined, - categoryId: categoryFilter || undefined, - suitableFor: suitableForFilter || undefined, - sortBy, - sortOrder - } - }); - if (prodRes.data?.data) { - const prodList = prodRes.data.data; - setProducts(prodList); - setTotalPages(prodRes.data.meta?.lastPage || 1); - - // Auto-open modal if URL specifies modal - const modalParam = searchParams.get('modal'); - const prodIdParam = searchParams.get('productId'); - const tabParam = searchParams.get('tab'); - if (modalParam === 'create') { - openModal(null); - if (tabParam) setActiveTab(tabParam); - } else if (modalParam === 'edit' && prodIdParam) { - const found = prodList.find((p: Product) => p.id === prodIdParam); - if (found) { - openModal(found); - if (tabParam) setActiveTab(tabParam); - } - } - } - - // Fetch categories separately - try { - const catRes = await api.get('/admin/categories'); - if (catRes.data?.data) { - setCategories(catRes.data.data); - } else if (Array.isArray(catRes.data)) { - setCategories(catRes.data); - } - } catch (err) { - console.warn('Could not fetch categories', err); - } - } catch (err: unknown) { - console.error(err); - toast.error('خطا در دریافت لیست محصولات'); - } finally { - setIsLoading(false); - } - }, [page, search, categoryFilter, suitableForFilter, sortBy, sortOrder, searchParams]); - - useEffect(() => { - const timer = setTimeout(() => { - fetchData(); - }, 400); - return () => clearTimeout(timer); - }, [fetchData]); - const openModal = (product: Product | null = null) => { setMediaImageError(false); if (product) { @@ -338,6 +276,69 @@ export default function Products() { updateUrlParams({ tab: newTab }); }; + const fetchData = useCallback(async () => { + try { + setIsLoading(true); + // Fetch products with full query params + const prodRes = await api.get('/admin/products', { + params: { + page, + limit, + search: search || undefined, + categoryId: categoryFilter || undefined, + suitableFor: suitableForFilter || undefined, + sortBy, + sortOrder + } + }); + if (prodRes.data?.data) { + const prodList = prodRes.data.data; + setProducts(prodList); + setTotalPages(prodRes.data.meta?.lastPage || 1); + + // Auto-open modal if URL specifies modal + const modalParam = searchParams.get('modal'); + const prodIdParam = searchParams.get('productId'); + const tabParam = searchParams.get('tab'); + if (modalParam === 'create') { + openModal(null); + if (tabParam) setActiveTab(tabParam); + } else if (modalParam === 'edit' && prodIdParam) { + const found = prodList.find((p: Product) => p.id === prodIdParam); + if (found) { + openModal(found); + if (tabParam) setActiveTab(tabParam); + } + } + } + + // Fetch categories separately + try { + const catRes = await api.get('/admin/categories'); + if (catRes.data?.data) { + setCategories(catRes.data.data); + } else if (Array.isArray(catRes.data)) { + setCategories(catRes.data); + } + } catch (err) { + console.warn('Could not fetch categories', err); + } + } catch (err: unknown) { + console.error(err); + toast.error('خطا در دریافت لیست محصولات'); + } finally { + setIsLoading(false); + } + }, [page, search, categoryFilter, suitableForFilter, sortBy, sortOrder, searchParams]); + + useEffect(() => { + const timer = setTimeout(() => { + fetchData(); + }, 400); + return () => clearTimeout(timer); + }, [fetchData]); + + const handleSave = async (e: React.FormEvent) => { e.preventDefault(); diff --git a/frontend/admin-panel/src/pages/Transactions.tsx b/frontend/admin-panel/src/pages/Transactions.tsx index 14db359..a6b2c3b 100644 --- a/frontend/admin-panel/src/pages/Transactions.tsx +++ b/frontend/admin-panel/src/pages/Transactions.tsx @@ -148,8 +148,7 @@ export default function Transactions() { const [isSubmittingRefund, setIsSubmittingRefund] = useState(false); const openReceiptModal = (tx: Transaction) => { - setParam('modal', 'receipt'); - setParam('txId', tx.id); + setMultipleParams({ modal: 'receipt', txId: tx.id }, false); setReceiptData({ trackId: tx.trackId || '-', refNumber: tx.refNumber || '-', @@ -167,6 +166,7 @@ export default function Transactions() { hour: '2-digit', minute: '2-digit', }), + fee: 0, mobile: tx.user?.mobile || '-', description: tx.description || 'پرداخت سفارش آنلاین کنینا', psp: tx.gateway === 'zibal' ? 'زیبال / به‌پرداخت ملت' : tx.gateway, @@ -182,8 +182,7 @@ export default function Transactions() { }; const openDetailsModal = (tx: Transaction) => { - setParam('modal', 'details'); - setParam('txId', tx.id); + setMultipleParams({ modal: 'details', txId: tx.id }, false); setSelectedTx(tx); setLiveInquiryData(null); setShowRawLogs(false); @@ -217,8 +216,7 @@ export default function Transactions() { }; const openRefundModal = (tx: Transaction) => { - setParam('modal', 'refund'); - setParam('txId', tx.id); + setMultipleParams({ modal: 'refund', txId: tx.id }, false); setRefundModalTx(tx); setRefundTarget(tx.type === 'WALLET_TOPUP' || !tx.trackId ? 'wallet' : 'wallet'); setRefundAmount(String(tx.amount || '')); @@ -245,12 +243,13 @@ export default function Transactions() { toast.error('کاربر مرتبط با این تراکنش یافت نشد'); return; } - await api.post(`/users/${userId}/wallet/adjust`, { + await api.post(`/admin/users/${userId}/wallet-adjust`, { amount: parsedAmount, - type: 'CREDIT', + type: 'refund', description: refundReason || `استرداد وجه تراکنش #${refundModalTx.trackId || refundModalTx.id.slice(0, 8)}`, }); toast.success('مبلغ با موفقیت به کیف پول کاربر عودت داده شد'); + } else { if (!refundModalTx.trackId) { toast.error('شناسه تراکنش زیبال (Track ID) برای این پرداخت موجود نیست'); @@ -466,13 +465,12 @@ export default function Transactions() { {/* Summary Stat Cards - 2 cols on mobile (2 rows), 4 cols on desktop */} {stats && ( -

حجم کل تراکنش‌های موفق

- {Number(stats.totalVolume).toLocaleString('fa-IR')}{' '} + {(Number(stats.totalVolume) || 0).toLocaleString('fa-IR')}{' '} تومان

@@ -485,7 +483,7 @@ export default function Transactions() {

پرداخت‌های موفق امروز

- {Number(stats.todayVolume).toLocaleString('fa-IR')}{' '} + {(Number(stats.todayVolume) || 0).toLocaleString('fa-IR')}{' '} تومان

@@ -498,7 +496,7 @@ export default function Transactions() {

نرخ موفقیت پرداخت‌ها

- ٪{Number(stats.successRate).toLocaleString('fa-IR')} + ٪{(Number(stats.successRate) || 0).toLocaleString('fa-IR')}

@@ -510,9 +508,9 @@ export default function Transactions() {

وضعیت تراکنش‌ها

- ✓ {stats.verifiedCount} - ⏳ {stats.pendingCount} - ✗ {stats.failedCount} + ✓ {stats.verifiedCount || 0} + ⏳ {stats.pendingCount || 0} + ✗ {stats.failedCount || 0}
@@ -522,6 +520,7 @@ export default function Transactions() {
)} + {/* Filter and Search Bar */}
diff --git a/frontend/admin-panel/src/pages/Users.tsx b/frontend/admin-panel/src/pages/Users.tsx index 9a45285..afab8cb 100644 --- a/frontend/admin-panel/src/pages/Users.tsx +++ b/frontend/admin-panel/src/pages/Users.tsx @@ -103,50 +103,6 @@ export default function Users() { const [isDeleting, setIsDeleting] = useState(false); const [isAdjustingWallet, setIsAdjustingWallet] = useState(false); - const fetchUsers = useCallback(async () => { - try { - setIsLoading(true); - const params = new URLSearchParams(); - params.append('page', page.toString()); - params.append('limit', '10'); - if (search) params.append('search', search); - if (role) params.append('role', role); - - const response = await api.get(`/admin/users?${params.toString()}`); - if (response.data?.success) { - const userList = response.data.data || []; - setUsers(userList); - setTotalPages(response.data.meta?.lastPage || 1); - setTotalCount(response.data.meta?.total || 0); - - const modalParam = searchParams.get('modal'); - const userId = searchParams.get('userId'); - if (modalParam === 'create') { - handleOpenCreate(); - } else if (userId && userList.length > 0) { - const found = userList.find((u: UserRecord) => u.id === userId); - if (found) { - if (modalParam === 'view') handleOpenView(found); - else if (modalParam === 'edit') handleOpenEdit(found); - else if (modalParam === 'wallet') handleOpenWallet(found); - } - } - } - } catch (err) { - console.error('Failed to fetch users', err); - } finally { - setIsLoading(false); - } - }, [page, search, role, searchParams]); - - useEffect(() => { - const delayDebounceFn = setTimeout(() => { - fetchUsers(); - }, 400); - - return () => clearTimeout(delayDebounceFn); - }, [fetchUsers]); - // Open View Modal const handleOpenView = (user: UserRecord) => { setViewUser(user); @@ -209,7 +165,49 @@ export default function Users() { setUserToDelete(null); }; + const fetchUsers = useCallback(async () => { + try { + setIsLoading(true); + const params = new URLSearchParams(); + params.append('page', page.toString()); + params.append('limit', '10'); + if (search) params.append('search', search); + if (role) params.append('role', role); + const response = await api.get(`/admin/users?${params.toString()}`); + if (response.data?.success) { + const userList = response.data.data || []; + setUsers(userList); + setTotalPages(response.data.meta?.lastPage || 1); + setTotalCount(response.data.meta?.total || 0); + + const modalParam = searchParams.get('modal'); + const userId = searchParams.get('userId'); + if (modalParam === 'create') { + handleOpenCreate(); + } else if (userId && userList.length > 0) { + const found = userList.find((u: UserRecord) => u.id === userId); + if (found) { + if (modalParam === 'view') handleOpenView(found); + else if (modalParam === 'edit') handleOpenEdit(found); + else if (modalParam === 'wallet') handleOpenWallet(found); + } + } + } + } catch (err) { + console.error('Failed to fetch users', err); + } finally { + setIsLoading(false); + } + }, [page, search, role, searchParams]); + + useEffect(() => { + const delayDebounceFn = setTimeout(() => { + fetchUsers(); + }, 400); + + return () => clearTimeout(delayDebounceFn); + }, [fetchUsers]); // Create User const handleCreateUser = async (e: React.FormEvent) => { diff --git a/frontend/admin-panel/src/pages/Wiki.tsx b/frontend/admin-panel/src/pages/Wiki.tsx index 4d71456..13c1186 100644 --- a/frontend/admin-panel/src/pages/Wiki.tsx +++ b/frontend/admin-panel/src/pages/Wiki.tsx @@ -62,6 +62,35 @@ export default function Wiki() { keywords: '' }); + const openModal = (term: WikiTerm | null = null) => { + if (term) { + setEditingTerm(term); + updateUrlParams({ modal: 'edit', termKey: term.key }); + setFormData({ + key: term.key, + term: term.term, + definition: term.definition, + relatedProducts: Array.isArray(term.relatedProducts) ? term.relatedProducts : [], + wikiId: term.wikiId || 'general', + metaTitle: term.metaTitle || '', + metaDescription: term.metaDescription || '', + keywords: term.keywords || '' + }); + } else { + setEditingTerm(null); + updateUrlParams({ modal: 'create', termKey: undefined }); + setFormData({ + key: '', term: '', definition: '', relatedProducts: [], wikiId: 'general', metaTitle: '', metaDescription: '', keywords: '' + }); + } + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + updateUrlParams({ modal: undefined, termKey: undefined }); + }; + const fetchTerms = useCallback(async () => { try { setIsLoading(true); @@ -132,34 +161,6 @@ export default function Wiki() { .catch(err => console.error(err)); }, []); - const openModal = (term: WikiTerm | null = null) => { - if (term) { - setEditingTerm(term); - updateUrlParams({ modal: 'edit', termKey: term.key }); - setFormData({ - key: term.key, - term: term.term, - definition: term.definition, - relatedProducts: Array.isArray(term.relatedProducts) ? term.relatedProducts : [], - wikiId: term.wikiId || 'general', - metaTitle: term.metaTitle || '', - metaDescription: term.metaDescription || '', - keywords: term.keywords || '' - }); - } else { - setEditingTerm(null); - updateUrlParams({ modal: 'create', termKey: undefined }); - setFormData({ - key: '', term: '', definition: '', relatedProducts: [], wikiId: 'general', metaTitle: '', metaDescription: '', keywords: '' - }); - } - setIsModalOpen(true); - }; - - const closeModal = () => { - setIsModalOpen(false); - updateUrlParams({ modal: undefined, termKey: undefined }); - }; return (