fix: resolve variable declaration order, modal state and lint errors
All checks were successful
Deploy Canina / deploy (push) Successful in 41s
All checks were successful
Deploy Canina / deploy (push) Successful in 41s
This commit is contained in:
parent
fc324aacaf
commit
b2b80849ea
@ -157,12 +157,20 @@ export class AdminController {
|
|||||||
return { success: true, ...result };
|
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')
|
@Put('orders/:id/status')
|
||||||
@ApiOperation({ summary: 'بروزرسانی وضعیت سفارش' })
|
@ApiOperation({ summary: 'بروزرسانی وضعیت سفارش' })
|
||||||
async updateOrderStatus(
|
async updateOrderStatus(
|
||||||
@Param('id') id: string,
|
@Param('id') id: string,
|
||||||
@Body('status') status: string,
|
@Body('status') status: string,
|
||||||
@Body('trackingCode') trackingCode?: string,
|
@Body('trackingCode') trackingCode?: string,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
const order = await this.adminService.updateOrderStatus(
|
const order = await this.adminService.updateOrderStatus(
|
||||||
id,
|
id,
|
||||||
|
|||||||
@ -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) {
|
async updateOrderStatus(id: string, status: string, trackingNumber?: string) {
|
||||||
const dataToUpdate: Prisma.OrderUpdateInput = { status };
|
const dataToUpdate: Prisma.OrderUpdateInput = { status };
|
||||||
|
|
||||||
if (trackingNumber !== undefined) {
|
if (trackingNumber !== undefined) {
|
||||||
dataToUpdate.trackingNumber = trackingNumber;
|
dataToUpdate.trackingNumber = trackingNumber;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,6 +65,35 @@ export default function Blogs() {
|
|||||||
imageUrl: ''
|
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 () => {
|
const fetchBlogs = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
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 (
|
return (
|
||||||
|
|||||||
@ -59,6 +59,34 @@ export default function Categories() {
|
|||||||
imageUrl: ''
|
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 () => {
|
const fetchCategories = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
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 (
|
return (
|
||||||
|
|||||||
@ -72,6 +72,36 @@ export default function Coupons() {
|
|||||||
setSearchParams(cleaned, { replace: true });
|
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 () => {
|
const fetchData = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -102,35 +132,6 @@ export default function Coupons() {
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}, [fetchData]);
|
}, [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) => {
|
const handleSave = async (e: React.FormEvent) => {
|
||||||
|
|||||||
@ -75,33 +75,6 @@ export default function MenuManager() {
|
|||||||
badge: '',
|
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(
|
const currentItems = allItems.filter(
|
||||||
(item) => item.menuType === activeTab && item.parentId === null
|
(item) => item.menuType === activeTab && item.parentId === null
|
||||||
);
|
);
|
||||||
@ -144,6 +117,34 @@ export default function MenuManager() {
|
|||||||
updateUrlState({ modal: null, itemId: null });
|
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) => {
|
const handleSave = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@ -212,6 +212,40 @@ export default function Orders() {
|
|||||||
const [modalStatus, setModalStatus] = useState('');
|
const [modalStatus, setModalStatus] = useState('');
|
||||||
const [isSavingTracking, setIsSavingTracking] = useState(false);
|
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 () => {
|
const fetchOrders = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -238,7 +272,18 @@ export default function Orders() {
|
|||||||
const modalType = searchParams.get('modal');
|
const modalType = searchParams.get('modal');
|
||||||
const orderIdParam = searchParams.get('orderId');
|
const orderIdParam = searchParams.get('orderId');
|
||||||
if (modalType && orderIdParam) {
|
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 (foundOrder) {
|
||||||
if (modalType === 'details') {
|
if (modalType === 'details') {
|
||||||
setSelectedOrder(foundOrder);
|
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 () => {
|
const handleSaveModalChanges = async () => {
|
||||||
if (!selectedOrder) return;
|
if (!selectedOrder) return;
|
||||||
try {
|
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 () => {
|
const handleExecuteOrderRefund = async () => {
|
||||||
@ -848,15 +860,20 @@ export default function Orders() {
|
|||||||
<div>
|
<div>
|
||||||
<div className="text-xs font-bold text-gray-400">زمان ثبت سفارش:</div>
|
<div className="text-xs font-bold text-gray-400">زمان ثبت سفارش:</div>
|
||||||
<div className="text-sm font-black text-gray-900">
|
<div className="text-sm font-black text-gray-900">
|
||||||
{toPersianDigits(new Date(selectedOrder.createdAt).toLocaleDateString('fa-IR', {
|
{selectedOrder.createdAt
|
||||||
weekday: 'long',
|
? toPersianDigits(
|
||||||
year: 'numeric',
|
new Date(selectedOrder.createdAt).toLocaleDateString('fa-IR', {
|
||||||
month: 'long',
|
weekday: 'long',
|
||||||
day: 'numeric',
|
year: 'numeric',
|
||||||
hour: '2-digit',
|
month: 'long',
|
||||||
minute: '2-digit',
|
day: 'numeric',
|
||||||
}))}
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
: '-'}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -195,68 +195,6 @@ export default function Products() {
|
|||||||
preorderDeposit: '' as number | string
|
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) => {
|
const openModal = (product: Product | null = null) => {
|
||||||
setMediaImageError(false);
|
setMediaImageError(false);
|
||||||
if (product) {
|
if (product) {
|
||||||
@ -338,6 +276,69 @@ export default function Products() {
|
|||||||
updateUrlParams({ tab: newTab });
|
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) => {
|
const handleSave = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|||||||
@ -148,8 +148,7 @@ export default function Transactions() {
|
|||||||
const [isSubmittingRefund, setIsSubmittingRefund] = useState(false);
|
const [isSubmittingRefund, setIsSubmittingRefund] = useState(false);
|
||||||
|
|
||||||
const openReceiptModal = (tx: Transaction) => {
|
const openReceiptModal = (tx: Transaction) => {
|
||||||
setParam('modal', 'receipt');
|
setMultipleParams({ modal: 'receipt', txId: tx.id }, false);
|
||||||
setParam('txId', tx.id);
|
|
||||||
setReceiptData({
|
setReceiptData({
|
||||||
trackId: tx.trackId || '-',
|
trackId: tx.trackId || '-',
|
||||||
refNumber: tx.refNumber || '-',
|
refNumber: tx.refNumber || '-',
|
||||||
@ -167,6 +166,7 @@ export default function Transactions() {
|
|||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit',
|
minute: '2-digit',
|
||||||
}),
|
}),
|
||||||
|
fee: 0,
|
||||||
mobile: tx.user?.mobile || '-',
|
mobile: tx.user?.mobile || '-',
|
||||||
description: tx.description || 'پرداخت سفارش آنلاین کنینا',
|
description: tx.description || 'پرداخت سفارش آنلاین کنینا',
|
||||||
psp: tx.gateway === 'zibal' ? 'زیبال / بهپرداخت ملت' : tx.gateway,
|
psp: tx.gateway === 'zibal' ? 'زیبال / بهپرداخت ملت' : tx.gateway,
|
||||||
@ -182,8 +182,7 @@ export default function Transactions() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openDetailsModal = (tx: Transaction) => {
|
const openDetailsModal = (tx: Transaction) => {
|
||||||
setParam('modal', 'details');
|
setMultipleParams({ modal: 'details', txId: tx.id }, false);
|
||||||
setParam('txId', tx.id);
|
|
||||||
setSelectedTx(tx);
|
setSelectedTx(tx);
|
||||||
setLiveInquiryData(null);
|
setLiveInquiryData(null);
|
||||||
setShowRawLogs(false);
|
setShowRawLogs(false);
|
||||||
@ -217,8 +216,7 @@ export default function Transactions() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const openRefundModal = (tx: Transaction) => {
|
const openRefundModal = (tx: Transaction) => {
|
||||||
setParam('modal', 'refund');
|
setMultipleParams({ modal: 'refund', txId: tx.id }, false);
|
||||||
setParam('txId', tx.id);
|
|
||||||
setRefundModalTx(tx);
|
setRefundModalTx(tx);
|
||||||
setRefundTarget(tx.type === 'WALLET_TOPUP' || !tx.trackId ? 'wallet' : 'wallet');
|
setRefundTarget(tx.type === 'WALLET_TOPUP' || !tx.trackId ? 'wallet' : 'wallet');
|
||||||
setRefundAmount(String(tx.amount || ''));
|
setRefundAmount(String(tx.amount || ''));
|
||||||
@ -245,12 +243,13 @@ export default function Transactions() {
|
|||||||
toast.error('کاربر مرتبط با این تراکنش یافت نشد');
|
toast.error('کاربر مرتبط با این تراکنش یافت نشد');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await api.post(`/users/${userId}/wallet/adjust`, {
|
await api.post(`/admin/users/${userId}/wallet-adjust`, {
|
||||||
amount: parsedAmount,
|
amount: parsedAmount,
|
||||||
type: 'CREDIT',
|
type: 'refund',
|
||||||
description: refundReason || `استرداد وجه تراکنش #${refundModalTx.trackId || refundModalTx.id.slice(0, 8)}`,
|
description: refundReason || `استرداد وجه تراکنش #${refundModalTx.trackId || refundModalTx.id.slice(0, 8)}`,
|
||||||
});
|
});
|
||||||
toast.success('مبلغ با موفقیت به کیف پول کاربر عودت داده شد');
|
toast.success('مبلغ با موفقیت به کیف پول کاربر عودت داده شد');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (!refundModalTx.trackId) {
|
if (!refundModalTx.trackId) {
|
||||||
toast.error('شناسه تراکنش زیبال (Track ID) برای این پرداخت موجود نیست');
|
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 */}
|
{/* Summary Stat Cards - 2 cols on mobile (2 rows), 4 cols on desktop */}
|
||||||
{stats && (
|
{stats && (
|
||||||
|
|
||||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-4">
|
<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="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">
|
<div className="min-w-0">
|
||||||
<p className="text-[11px] sm:text-xs font-bold text-gray-500 truncate">حجم کل تراکنشهای موفق</p>
|
<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">
|
<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>
|
<span className="text-[10px] sm:text-xs font-normal text-gray-400">تومان</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -485,7 +483,7 @@ export default function Transactions() {
|
|||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-[11px] sm:text-xs font-bold text-gray-500 truncate">پرداختهای موفق امروز</p>
|
<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">
|
<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>
|
<span className="text-[10px] sm:text-xs font-normal text-gray-400">تومان</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@ -498,7 +496,7 @@ export default function Transactions() {
|
|||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-[11px] sm:text-xs font-bold text-gray-500 truncate">نرخ موفقیت پرداختها</p>
|
<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">
|
<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>
|
</p>
|
||||||
</div>
|
</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">
|
<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">
|
<div className="min-w-0">
|
||||||
<p className="text-[11px] sm:text-xs font-bold text-gray-500 truncate">وضعیت تراکنشها</p>
|
<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">
|
<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-emerald-600">✓ {stats.verifiedCount || 0}</span>
|
||||||
<span className="text-amber-500">⏳ {stats.pendingCount}</span>
|
<span className="text-amber-500">⏳ {stats.pendingCount || 0}</span>
|
||||||
<span className="text-rose-600">✗ {stats.failedCount}</span>
|
<span className="text-rose-600">✗ {stats.failedCount || 0}</span>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
{/* Filter and Search Bar */}
|
{/* Filter and Search Bar */}
|
||||||
<div className="bg-white p-5 rounded-2xl border border-gray-200 shadow-xs space-y-4">
|
<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">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3">
|
||||||
|
|||||||
@ -103,50 +103,6 @@ export default function Users() {
|
|||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
const [isAdjustingWallet, setIsAdjustingWallet] = 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
|
// Open View Modal
|
||||||
const handleOpenView = (user: UserRecord) => {
|
const handleOpenView = (user: UserRecord) => {
|
||||||
setViewUser(user);
|
setViewUser(user);
|
||||||
@ -209,7 +165,49 @@ export default function Users() {
|
|||||||
setUserToDelete(null);
|
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
|
// Create User
|
||||||
const handleCreateUser = async (e: React.FormEvent) => {
|
const handleCreateUser = async (e: React.FormEvent) => {
|
||||||
|
|||||||
@ -62,6 +62,35 @@ export default function Wiki() {
|
|||||||
keywords: ''
|
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 () => {
|
const fetchTerms = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
@ -132,34 +161,6 @@ export default function Wiki() {
|
|||||||
.catch(err => console.error(err));
|
.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 (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user