diff --git a/src/pages/products/Questions.tsx b/src/pages/products/Questions.tsx new file mode 100644 index 0000000..1d92fb7 --- /dev/null +++ b/src/pages/products/Questions.tsx @@ -0,0 +1,160 @@ +import { type FC, useState } from 'react' +import { useGetProductQuestions } from './hooks/useProductData' +import { type ProductQuestionType } from './types/Types' +import PageLoading from '../../components/PageLoading' +import Error from '../../components/Error' +import PageTitle from '../../components/PageTitle' +import Td from '../../components/Td' +import PaginationUi from '../../components/PaginationUi' +import StatusWithText from '../../components/StatusWithText' +import { QuestionModal } from './components' +import { Eye } from 'iconsax-react' + +const Questions: FC = () => { + const [currentPage, setCurrentPage] = useState(1); + const [selectedQuestion, setSelectedQuestion] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + const { data, isLoading, error } = useGetProductQuestions(currentPage); + + if (isLoading) { + return ( +
+ +
+ ); + } + + if (error) { + return ( +
+ +
+ ); + } + + const questions = data?.results?.questions || []; + const pager = data?.results?.pager; + + const getStatusVariant = (status: string) => { + switch (status) { + case 'accepted': return 'success'; + case 'rejected': return 'error'; + case 'pending': return 'warning'; + default: return 'warning'; + } + }; + + const getStatusText = (status: string) => { + switch (status) { + case 'accepted': return 'تایید شده'; + case 'rejected': return 'رد شده'; + case 'pending': return 'در انتظار'; + default: return status; + } + }; + + const handleViewQuestion = (question: ProductQuestionType) => { + setSelectedQuestion(question); + setIsModalOpen(true); + }; + + const handleCloseModal = () => { + setIsModalOpen(false); + setSelectedQuestion(null); + }; + + return ( +
+ +
+ + + + + + + {questions.length === 0 ? ( + + + + ) : ( + questions.map((question: ProductQuestionType) => ( + + + + + + + + )) + )} + +
+ + + + + +
+ هیچ سوالی یافت نشد +
+
+ {question.user?.[0]?.fullName || 'کاربر ناشناس'} +
+
+
+
+ {question.product?.title_fa} +
+
+
+ {question.product?.title_fa} +
+
+ {question.product?.title_en} +
+
+
+
+
+ {question.content} +
+
+ + + +
+ handleViewQuestion(question)} + /> +
+
+
+ + { + setCurrentPage(page); + }} + /> + + +
+ ) +} + +export default Questions \ No newline at end of file diff --git a/src/pages/products/components/QuestionModal.tsx b/src/pages/products/components/QuestionModal.tsx new file mode 100644 index 0000000..6db982a --- /dev/null +++ b/src/pages/products/components/QuestionModal.tsx @@ -0,0 +1,147 @@ +import { type FC } from 'react' +import { type ProductQuestionType } from '../types/Types' +import DefaulModal from '../../../components/DefaulModal' +import StatusWithText from '../../../components/StatusWithText' +import { Button } from '@/components/ui/button' +import { TickCircle } from 'iconsax-react' + +interface QuestionModalProps { + isOpen: boolean + onClose: () => void + question: ProductQuestionType | null + onApprove?: (questionId: string) => void + isApproving?: boolean +} + +const QuestionModal: FC = ({ + isOpen, + onClose, + question, + onApprove, + isApproving = false +}) => { + const getStatusVariant = (status: string) => { + switch (status) { + case 'accepted': return 'success'; + case 'rejected': return 'error'; + case 'pending': return 'warning'; + default: return 'warning'; + } + }; + + const getStatusText = (status: string) => { + switch (status) { + case 'accepted': return 'تایید شده'; + case 'rejected': return 'رد شده'; + case 'pending': return 'در انتظار'; + default: return status; + } + }; + + const handleApprove = () => { + if (question && onApprove) { + onApprove(question._id); + } + }; + + return ( + + {question && ( +
+ {/* اطلاعات کاربر و محصول */} +
+
+
+ {question.product?.title_fa} +
+
+
+ {question.product?.title_fa} +
+
+ توسط {question.user?.[0]?.fullName || 'کاربر ناشناس'} +
+
+
+
+ + {/* محتوای سوال */} +
+

متن سوال:

+

{question.content}

+
+ + {/* پاسخ‌ها */} + {question.answers && question.answers.length > 0 && ( +
+

پاسخ‌ها:

+
+ {question.answers.map((answer, index) => ( +
+
+ + {answer.user?.fullName || 'مدیر'} + + {answer.status && ( + + )} +
+

{answer.content}

+ {answer.createdAt && ( +
+ {answer.createdAt} +
+ )} +
+ ))} +
+
+ )} + + {/* وضعیت */} +
+ وضعیت: + +
+ + {/* تاریخ */} +
+ تاریخ ارسال: {question.createdAt} +
+ + {/* دکمه تایید */} + {question.status !== 'accepted' && onApprove && ( +
+ +
+ )} +
+ )} +
+ ) +} + +export default QuestionModal diff --git a/src/pages/products/components/index.ts b/src/pages/products/components/index.ts index ad159e9..ed7829b 100644 --- a/src/pages/products/components/index.ts +++ b/src/pages/products/components/index.ts @@ -3,3 +3,4 @@ export { default as Step1Form } from "./Step1Form"; export { default as Step2Form } from "./Step2Form"; export { default as Step3Form } from "./Step3Form"; export { default as ReviewModal } from "./ReviewModal"; +export { default as QuestionModal } from "./QuestionModal"; diff --git a/src/pages/products/hooks/useProductData.ts b/src/pages/products/hooks/useProductData.ts index 3adcec8..8dcccac 100644 --- a/src/pages/products/hooks/useProductData.ts +++ b/src/pages/products/hooks/useProductData.ts @@ -116,4 +116,11 @@ export const useApproveProductReview = () => { return useMutation({ mutationFn: api.approveProductReview, }); +}; + +export const useGetProductQuestions = (page: number = 1, limit: number = 10) => { + return useQuery({ + queryKey: ['product-questions', page, limit], + queryFn: () => api.getProductQuestions(page, limit), + }); }; \ No newline at end of file diff --git a/src/pages/products/service/ProductService.ts b/src/pages/products/service/ProductService.ts index 3b89b81..8cfe67d 100644 --- a/src/pages/products/service/ProductService.ts +++ b/src/pages/products/service/ProductService.ts @@ -9,6 +9,7 @@ import { type GetProductsResponseType, type GetProductVariantsResponseType, type GetProductReviewsResponseType, + type ProductQuestionsResponseType, type CreateProductVariantRequestType, type CreateProductVariantResponseType, type GetProductByIdResponseType, @@ -110,4 +111,10 @@ export const getProductReviews = async (page: number = 1, limit: number = 10): P export const approveProductReview = async (id: string) => { const { data } = await axios.post(`/admin/products/comments/${id}/approve`); return data; +}; + +export const getProductQuestions = async (page: number = 1, limit: number = 10): Promise => { + const params: Record = { page, limit }; + const { data } = await axios.get(`admin/products/questions`, { params }); + return data; }; \ No newline at end of file diff --git a/src/pages/products/types/Types.ts b/src/pages/products/types/Types.ts index 15dc202..7ab4a85 100644 --- a/src/pages/products/types/Types.ts +++ b/src/pages/products/types/Types.ts @@ -456,6 +456,72 @@ export type GetProductReviewsResponseType = { }; }; +// تایپ‌های مربوط به سوالات محصول +export type ProductQuestionUserType = { + fullName: string; +}; + +export type ProductQuestionBrandType = { + _id: string; +}; + +export type ProductQuestionCategoryType = { + _id: string; +}; + +export type ProductQuestionVariantType = { + _id: string; +}; + +export type ProductQuestionProductType = { + _id: number; + title_fa: string; + title_en: string; + seoTitle: string; + seoDescription: string | null; + source: string; + description: string; + metaDescription: string; + tags: string[]; + advantages: string[]; + disAdvantages: string[]; + imagesUrl: ProductImagesType; + isFake: string; + voice: string | null; + specifications: ProductSpecificationType[]; + brand: ProductQuestionBrandType; + category: ProductQuestionCategoryType; + variants: ProductQuestionVariantType[]; +}; + +export type ProductQuestionAnswerType = { + _id?: string; + content: string; + status?: string; + user?: ProductQuestionUserType; + createdAt?: string; +}; + +export type ProductQuestionType = { + _id: string; + content: string; + status: string; + product: ProductQuestionProductType; + user: ProductQuestionUserType[]; + answers: ProductQuestionAnswerType[]; + createdAt: string; +}; + +export type ProductQuestionsResponseType = { + status: number; + success: boolean; + results: { + pager: PagerType; + questions: ProductQuestionType[]; + count: number; + }; +}; + // تایپ‌های مربوط به آپدیت محصول سه مرحله‌ای // مرحله اول: آپدیت جزئیات محصول diff --git a/src/router/Main.tsx b/src/router/Main.tsx index bca73bf..b18150d 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -78,6 +78,7 @@ import CreateCategory from '@/pages/ticket/Create' import UpdateCategory from '@/pages/ticket/Update' import TicketMessages from '@/pages/ticket/TicketMessages' import Reviews from '@/pages/products/Reviews' +import Questions from '@/pages/products/Questions' const MainRouter: FC = () => { const { hasSubMenu } = useSharedStore() @@ -190,6 +191,7 @@ const MainRouter: FC = () => { } /> } /> + } />