fix: resolve variable declaration order, modal state and lint errors
All checks were successful
Deploy Canina / deploy (push) Successful in 41s

This commit is contained in:
parsa aghaei 2026-08-23 10:55:09 +03:30
parent fc324aacaf
commit b2b80849ea
11 changed files with 353 additions and 302 deletions

View File

@ -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,

View File

@ -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;
}

View File

@ -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 (

View File

@ -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 (

View File

@ -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) => {

View File

@ -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();

View File

@ -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<Order | null>(null);
const [refundTarget, setRefundTarget] = useState<'wallet' | 'zibal'>('wallet');
const [refundAmount, setRefundAmount] = useState<string>('');
const [refundReason, setRefundReason] = useState<string>('');
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<Order | null>(null);
const [refundTarget, setRefundTarget] = useState<'wallet' | 'zibal'>('wallet');
const [refundAmount, setRefundAmount] = useState<string>('');
const [refundReason, setRefundReason] = useState<string>('');
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() {
<div>
<div className="text-xs font-bold text-gray-400">زمان ثبت سفارش:</div>
<div className="text-sm font-black text-gray-900">
{toPersianDigits(new Date(selectedOrder.createdAt).toLocaleDateString('fa-IR', {
{selectedOrder.createdAt
? toPersianDigits(
new Date(selectedOrder.createdAt).toLocaleDateString('fa-IR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
}))}
})
)
: '-'}
</div>
</div>
</div>

View File

@ -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();

View File

@ -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 && (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4">
<div className="bg-white p-4 sm:p-5 rounded-2xl border border-gray-200 shadow-xs flex items-center justify-between">
<div className="min-w-0">
<p className="text-[11px] sm:text-xs font-bold text-gray-500 truncate">حجم کل تراکنشهای موفق</p>
<p className="text-base sm:text-xl font-black text-gray-900 mt-1">
{Number(stats.totalVolume).toLocaleString('fa-IR')}{' '}
{(Number(stats.totalVolume) || 0).toLocaleString('fa-IR')}{' '}
<span className="text-[10px] sm:text-xs font-normal text-gray-400">تومان</span>
</p>
</div>
@ -485,7 +483,7 @@ export default function Transactions() {
<div className="min-w-0">
<p className="text-[11px] sm:text-xs font-bold text-gray-500 truncate">پرداختهای موفق امروز</p>
<p className="text-base sm:text-xl font-black text-emerald-600 mt-1">
{Number(stats.todayVolume).toLocaleString('fa-IR')}{' '}
{(Number(stats.todayVolume) || 0).toLocaleString('fa-IR')}{' '}
<span className="text-[10px] sm:text-xs font-normal text-gray-400">تومان</span>
</p>
</div>
@ -498,7 +496,7 @@ export default function Transactions() {
<div className="min-w-0">
<p className="text-[11px] sm:text-xs font-bold text-gray-500 truncate">نرخ موفقیت پرداختها</p>
<p className="text-base sm:text-xl font-black text-indigo-600 mt-1">
٪{Number(stats.successRate).toLocaleString('fa-IR')}
٪{(Number(stats.successRate) || 0).toLocaleString('fa-IR')}
</p>
</div>
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-indigo-50 text-indigo-600 rounded-2xl flex items-center justify-center shrink-0">
@ -510,9 +508,9 @@ export default function Transactions() {
<div className="min-w-0">
<p className="text-[11px] sm:text-xs font-bold text-gray-500 truncate">وضعیت تراکنشها</p>
<div className="flex flex-wrap items-center gap-2 mt-1 text-[10px] sm:text-xs font-bold">
<span className="text-emerald-600"> {stats.verifiedCount}</span>
<span className="text-amber-500"> {stats.pendingCount}</span>
<span className="text-rose-600"> {stats.failedCount}</span>
<span className="text-emerald-600"> {stats.verifiedCount || 0}</span>
<span className="text-amber-500"> {stats.pendingCount || 0}</span>
<span className="text-rose-600"> {stats.failedCount || 0}</span>
</div>
</div>
<div className="w-10 h-10 sm:w-12 sm:h-12 bg-gray-50 text-gray-600 rounded-2xl flex items-center justify-center shrink-0">
@ -522,6 +520,7 @@ export default function Transactions() {
</div>
)}
{/* Filter and Search Bar */}
<div className="bg-white p-5 rounded-2xl border border-gray-200 shadow-xs space-y-4">
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3">

View File

@ -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) => {

View File

@ -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 (
<div className="space-y-6">