From 72f84d00b0467c10326c23b25bce0cbb62407315 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Sun, 12 Oct 2025 11:38:23 +0330 Subject: [PATCH] request create product + category learning --- src/config/Pages.ts | 2 + src/pages/seller/CategoryLearning.tsx | 102 +++++++- .../components/CategoryLearningTableRow.tsx | 63 +++++ .../components/CreateUpdateCategoryModal.tsx | 111 +++++++++ .../components/ProductRequestDetailModal.tsx | 224 +----------------- src/pages/seller/hooks/useSellerData.ts | 49 ++++ src/pages/seller/service/SellerService.ts | 46 ++++ src/pages/seller/types/Types.ts | 24 ++ src/router/Main.tsx | 3 + src/shared/SideBar.tsx | 8 +- 10 files changed, 403 insertions(+), 229 deletions(-) create mode 100644 src/pages/seller/components/CategoryLearningTableRow.tsx create mode 100644 src/pages/seller/components/CreateUpdateCategoryModal.tsx diff --git a/src/config/Pages.ts b/src/config/Pages.ts index fcf342e..0d63f49 100644 --- a/src/config/Pages.ts +++ b/src/config/Pages.ts @@ -200,5 +200,7 @@ export const Pages = { withdrawalRequests: "/sellers/withdrawal-requests", announcement: "/sellers/announcement", requestCreateProduct: "/sellers/request-create-product", + learningCategory: "/sellers/learning-category", + learning: "/sellers/learning", }, }; diff --git a/src/pages/seller/CategoryLearning.tsx b/src/pages/seller/CategoryLearning.tsx index a254501..963ddaa 100644 --- a/src/pages/seller/CategoryLearning.tsx +++ b/src/pages/seller/CategoryLearning.tsx @@ -1,8 +1,104 @@ -import { type FC } from 'react' +import { type FC, useState } from 'react' +import { useGetLearningCategory } from './hooks/useSellerData' +import { type LearningCategory } from './types/Types' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import Td from '../../components/Td' +import Button from '@/components/Button' +import CategoryLearningTableRow from './components/CategoryLearningTableRow' +import CreateUpdateCategoryModal from './components/CreateUpdateCategoryModal' + +const CategoryLearning: FC = () => { + const [isModalOpen, setIsModalOpen] = useState(false) + const [selectedCategory, setSelectedCategory] = useState() + + const { data: categoriesData, isLoading, error, refetch } = useGetLearningCategory() + + const handleModalClose = () => { + setIsModalOpen(false) + setSelectedCategory(undefined) + } + + const handleModalSuccess = () => { + refetch() + } + + const handleEditCategory = (category: LearningCategory) => { + setSelectedCategory(category) + setIsModalOpen(true) + } + + const handleCreateCategory = () => { + setSelectedCategory(undefined) + setIsModalOpen(true) + } + + if (isLoading) { + return ( +
+ +
+ ) + } + + if (error) { + return ( +
+ +
+ ) + } + + const categories = categoriesData?.results?.learningCategory || [] -const CategoryLearning = () => { return ( -
CategoryLearning
+
+
+
+ +
+ + + + + + + {categories.length === 0 ? ( + + + + ) : ( + categories.map((category: LearningCategory) => ( + + )) + )} + +
+ + + + +
+ هیچ دسته‌بندی یادگیری یافت نشد +
+
+ + +
) } diff --git a/src/pages/seller/components/CategoryLearningTableRow.tsx b/src/pages/seller/components/CategoryLearningTableRow.tsx new file mode 100644 index 0000000..8229f45 --- /dev/null +++ b/src/pages/seller/components/CategoryLearningTableRow.tsx @@ -0,0 +1,63 @@ +import { type FC } from 'react' +import Td from '../../../components/Td' +import { Calendar, Edit } from 'iconsax-react' +import StatusWithText from '../../../components/StatusWithText' +import type { LearningCategory } from '../types/Types' + +interface Props { + category: LearningCategory + onEdit: (category: LearningCategory) => void +} + +const CategoryLearningTableRow: FC = ({ category, onEdit }) => { + const formatDate = (dateString: string) => { + return new Date(dateString).toLocaleDateString('fa-IR', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }) + } + + return ( + + +
+
+ {category.title} +
+
+ + +
+ + + {formatDate(category.createdAt)} + +
+ + +
+ + + {formatDate(category.updatedAt)} + +
+ + + + + +
+ onEdit(category)} color="#8C90A3" size={16} /> +
+ + + ) +} + +export default CategoryLearningTableRow diff --git a/src/pages/seller/components/CreateUpdateCategoryModal.tsx b/src/pages/seller/components/CreateUpdateCategoryModal.tsx new file mode 100644 index 0000000..5f58d21 --- /dev/null +++ b/src/pages/seller/components/CreateUpdateCategoryModal.tsx @@ -0,0 +1,111 @@ +import { type FC, useEffect } from 'react' +import DefaulModal from '@/components/DefaulModal' +import Input from '@/components/Input' +import Button from '@/components/Button' +import { useCreateLearningCategory, useUpdateLearningCategory } from '../hooks/useSellerData' +import { type LearningCategory, type CreateCategoryLearning } from '../types/Types' +import { useFormik } from 'formik' +import * as Yup from 'yup' +import { toast } from 'react-toastify' +import { extractErrorMessage } from '@/helpers/utils' + +interface Props { + open: boolean + close: () => void + onSuccess?: () => void + category?: LearningCategory +} + +const CreateUpdateCategoryModal: FC = ({ open, close, onSuccess, category }) => { + const isEdit = !!category + const createMutation = useCreateLearningCategory() + const updateMutation = useUpdateLearningCategory() + + const formik = useFormik({ + initialValues: { + title: category?.title || '' + }, + validationSchema: Yup.object({ + title: Yup.string().required('عنوان دسته‌بندی الزامی است') + }), + onSubmit: (values) => { + if (isEdit && category) { + // Update category + updateMutation.mutate({ + id: category._id, + params: values + }, { + onSuccess: () => { + toast.success('دسته‌بندی با موفقیت بروزرسانی شد') + close() + onSuccess?.() + formik.resetForm() + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } else { + // Create category + createMutation.mutate(values, { + onSuccess: () => { + toast.success('دسته‌بندی جدید با موفقیت ایجاد شد') + close() + onSuccess?.() + formik.resetForm() + }, + onError: (error) => { + toast.error(extractErrorMessage(error)) + } + }) + } + } + }) + + useEffect(() => { + if (open) { + formik.resetForm() + formik.setValues({ + title: category?.title || '' + }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open, category]) + + return ( + +
+ + +
+
+
+
+ ) +} + +export default CreateUpdateCategoryModal diff --git a/src/pages/seller/components/ProductRequestDetailModal.tsx b/src/pages/seller/components/ProductRequestDetailModal.tsx index 9c0411c..b968490 100644 --- a/src/pages/seller/components/ProductRequestDetailModal.tsx +++ b/src/pages/seller/components/ProductRequestDetailModal.tsx @@ -1,8 +1,6 @@ import { type FC } from 'react' import DefaulModal from '@/components/DefaulModal' import { type ProductRequest } from '../types/Types' -import StatusWithText from '@/components/StatusWithText' -import { Calendar, User as UserIcon, Shop, Tag, Box, MessageSquare, Location, Money } from 'iconsax-react' interface Props { open: boolean @@ -13,31 +11,6 @@ interface Props { const ProductRequestDetailModal: FC = ({ open, close, request }) => { if (!request) return null - const getStatusVariant = (status: string) => { - switch (status) { - case 'Approved': return 'success' - case 'Rejected': return 'error' - case 'Pending': return 'warning' - case 'Draft': return 'warning' - default: return 'warning' - } - } - - const getStatusText = (status: string) => { - switch (status) { - case 'Approved': return 'تایید شده' - case 'Rejected': return 'رد شده' - case 'Pending': return 'در انتظار' - case 'Draft': return 'پیش‌نویس' - default: return status - } - } - - const formatDate = (dateString: string) => { - if (!dateString) return '-' - return new Date(dateString).toLocaleDateString('fa-IR') - } - return ( = ({ open, close, request }) => { width={800} >
- {/* وضعیت درخواست */} -
-

وضعیت درخواست

- -
- - {/* اطلاعات فروشنده */} -
-

- - اطلاعات فروشنده -

-
-
- - نام: - {request.seller?.fullName || '-'} -
-
- - فروشگاه: - {request.shop?.shopName || '-'} -
-
- - آدرس: - {request.shop?.address?.province?.name || '-'}، {request.shop?.address?.city?.name || '-'} -
-
- - کد فروشگاه: - {request.shop?.shopCode || '-'} -
-
-
- - {/* اطلاعات محصول */} -
-

- - اطلاعات محصول -

-
-
- - نام محصول: - {request.productName} -
-
- - برند: - {request.brand?.title_fa || '-'} -
-
- - دسته‌بندی: - {request.category?.title_fa || '-'} -
-
- - منبع: - {request.source || '-'} -
-
-
- - {/* توضیحات */} -
-

توضیحات

-

{String(request.description || 'توضیحاتی وارد نشده')}

-
- - {/* ابعاد و وزن */} -
-

- - ابعاد و وزن -

-
-
-
{request.dimensions?.package_weight || 0}
-
وزن (گرم)
-
-
-
{request.dimensions?.package_length || 0}
-
طول (سانتی‌متر)
-
-
-
{request.dimensions?.package_width || 0}
-
عرض (سانتی‌متر)
-
-
-
{request.dimensions?.package_height || 0}
-
ارتفاع (سانتی‌متر)
-
-
-
- - {/* مزایا و معایب */} -
-
-

مزایا

- {request.advantages && request.advantages.length > 0 ? ( -
    - {request.advantages.map((advantage, index) => ( -
  • - - {String(advantage)} -
  • - ))} -
- ) : ( -

مزایایی وارد نشده

- )} -
- -
-

معایب

- {request.disAdvantages && request.disAdvantages.length > 0 ? ( -
    - {request.disAdvantages.map((disadvantage, index) => ( -
  • - - {String(disadvantage)} -
  • - ))} -
- ) : ( -

معایبی وارد نشده

- )} -
-
- - {/* خدمات درخواست شده */} -
-

خدمات درخواست شده

-
-
- - 📸 عکاسی محصول - {request.requestPhotosCount && ( - ({request.requestPhotosCount} عکس) - )} -
-
- - 👨‍💼 بررسی تخصصی -
-
- - 📦 ویدیو باز کردن بسته -
-
-
- - {/* اطلاعات پرداخت */} - {request.payment && ( -
-

- - اطلاعات پرداخت -

-
-
{String(JSON.stringify(request.payment, null, 2))}
-
-
- )} - - {/* تاریخ ایجاد */} -
- تاریخ ایجاد: -
- - {formatDate(request._id ? new Date().toISOString() : '')} -
-
+

در حال حاضر این modal در حال توسعه است.

) } -export default ProductRequestDetailModal +export default ProductRequestDetailModal \ No newline at end of file diff --git a/src/pages/seller/hooks/useSellerData.ts b/src/pages/seller/hooks/useSellerData.ts index f98f9f1..c743926 100644 --- a/src/pages/seller/hooks/useSellerData.ts +++ b/src/pages/seller/hooks/useSellerData.ts @@ -1,5 +1,9 @@ import * as api from "../service/SellerService"; import { useMutation, useQuery } from "@tanstack/react-query"; +import type { + CreateCategoryLearning, + CreateLearningType, +} from "../types/Types"; export const useGetSellers = (page: number = 1, enabled: boolean = true) => { return useQuery({ @@ -66,3 +70,48 @@ export const useGetRequestCreateProduct = (page: number = 1) => { queryFn: () => api.getRequestCreateProduct(page), }); }; + +export const useGetLearningCategory = () => { + return useQuery({ + queryKey: ["learning-category"], + queryFn: () => api.getLearningCategory(), + }); +}; + +export const useCreateLearningCategory = () => { + return useMutation({ + mutationFn: api.createLearningCategory, + }); +}; + +export const useUpdateLearningCategory = () => { + return useMutation({ + mutationFn: ({ + id, + params, + }: { + id: string; + params: CreateCategoryLearning; + }) => api.updateLearningCategory(id, params), + }); +}; + +export const useGetLearning = () => { + return useQuery({ + queryKey: ["learning"], + queryFn: () => api.getLearning(), + }); +}; + +export const useCreateLearning = () => { + return useMutation({ + mutationFn: api.createLearning, + }); +}; + +export const useUpdateLearning = () => { + return useMutation({ + mutationFn: ({ id, params }: { id: string; params: CreateLearningType }) => + api.updateLearning(id, params), + }); +}; diff --git a/src/pages/seller/service/SellerService.ts b/src/pages/seller/service/SellerService.ts index 3ab80df..11ac3bc 100644 --- a/src/pages/seller/service/SellerService.ts +++ b/src/pages/seller/service/SellerService.ts @@ -7,6 +7,9 @@ import type { NotificationsResponse, CreateAnnouncementSpecificSeller, GetRequestCreateProductResponse, + CreateCategoryLearning, + GetLearningCategoryResponse, + CreateLearningType, } from "../types/Types"; export const getSellers = async ( @@ -87,3 +90,46 @@ export const getRequestCreateProduct = async ( ); return data; }; + +export const getLearningCategory = async (): Promise< + GetLearningCategoryResponse +> => { + const { data } = await axios.get(`/learning/category`); + return data; +}; + +export const createLearningCategory = async ( + params: CreateCategoryLearning +) => { + const { data } = await axios.post(`/admin/learning/category`, params); + return data; +}; + +export const updateLearningCategory = async ( + id: string, + params: CreateCategoryLearning +) => { + const { data } = await axios.patch( + `/admin/learning/category/${id}/update`, + params + ); + return data; +}; + +export const getLearning = async () => { + const { data } = await axios.get(`/learning`); + return data; +}; + +export const createLearning = async (params: CreateLearningType) => { + const { data } = await axios.post(`/admin/learning`, params); + return data; +}; + +export const updateLearning = async ( + id: string, + params: CreateLearningType +) => { + const { data } = await axios.patch(`/admin/learning/${id}/update`, params); + return data; +}; diff --git a/src/pages/seller/types/Types.ts b/src/pages/seller/types/Types.ts index 457840f..225f37c 100644 --- a/src/pages/seller/types/Types.ts +++ b/src/pages/seller/types/Types.ts @@ -217,6 +217,22 @@ export interface CreateCategoryLearning { title: string; } +export interface LearningCategory { + _id: string; + title: string; + deleted: boolean; + createdAt: string; + updatedAt: string; +} + +export interface GetLearningCategoryResponse { + status: number; + success: boolean; + results: { + learningCategory: LearningCategory[]; + }; +} + // Product Request Types export interface City { _id: number; @@ -318,3 +334,11 @@ export interface GetRequestCreateProductResponse { pager: Pager; }; } + +export interface CreateLearningType { + title: string; + description: string; + videoUrl: string; + cover: string; + category: string; +} diff --git a/src/router/Main.tsx b/src/router/Main.tsx index effd1c3..fae0d6d 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -67,6 +67,7 @@ import WholeSaleRequest from '@/pages/seller/WholeSaleRequest' import WithdrawalRequestPage from '@/pages/seller/WithdrawalRequest' import Annoncement from '@/pages/seller/Annoncement' import RequestCreateProduct from '@/pages/seller/RequestCreateProduct' +import CategoryLearning from '@/pages/seller/CategoryLearning' const MainRouter: FC = () => { const { hasSubMenu } = useSharedStore() @@ -167,6 +168,8 @@ const MainRouter: FC = () => { } /> } /> } /> + } /> + {/* } /> */} diff --git a/src/shared/SideBar.tsx b/src/shared/SideBar.tsx index d54f000..d25c826 100644 --- a/src/shared/SideBar.tsx +++ b/src/shared/SideBar.tsx @@ -528,13 +528,13 @@ const SellersSubMenu: FC = () => { />