This commit is contained in:
hamid zarghami
2025-10-29 16:38:38 +03:30
parent 816b172704
commit 1b53833159
5 changed files with 90 additions and 27 deletions
+71 -23
View File
@@ -3,33 +3,42 @@ import { FC, useMemo, useState } from 'react'
import { Button } from '@/components/ui/button'
import { Separator } from '@/components/ui/separator'
import { Star1 } from 'iconsax-react'
import { Product, Review } from '@/types/product.types'
import { Product } from '@/types/product.types'
import AddComment from './AddComment'
import { useGetProductComments } from '../hooks/useProductData'
import PageLoading from '@/components/PageLoading'
import moment from 'moment-jalaali'
interface ReviewsProps {
product: Product
reviews?: Review[]
}
const Reviews: FC<ReviewsProps> = ({ reviews = [] }) => {
const Reviews: FC<ReviewsProps> = ({ product }) => {
const { data: commentsData, isLoading } = useGetProductComments(product._id)
const [sort, setSort] = useState<'new' | 'helpful'>('new')
const [open, setOpen] = useState(false)
const sortedReviews = useMemo(() => {
const comments = useMemo(() => commentsData?.results?.comments || [], [commentsData?.results?.comments])
const sortedComments = useMemo(() => {
if (sort === 'new') {
return [...reviews].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
return [...comments].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
}
return [...reviews].sort((a, b) => b.helpful - a.helpful)
}, [sort, reviews])
return [...comments].sort((a, b) => (b.status === 'approved' ? 1 : 0) - (a.status === 'approved' ? 1 : 0))
}, [sort, comments])
const approvedComments = useMemo(() => {
return comments.filter(comment => comment.status === 'approved')
}, [comments])
const average = useMemo(() => {
if (reviews.length === 0) return '0.0'
const sum = reviews.reduce((acc, r) => acc + r.rating, 0)
return (sum / reviews.length).toFixed(1)
}, [reviews])
if (approvedComments.length === 0) return '0.0'
const sum = approvedComments.reduce((acc, comment) => acc + comment.rate, 0)
return (sum / approvedComments.length).toFixed(1)
}, [approvedComments])
const formatDate = (dateString: string) => {
const date = new Date(dateString)
return date.toLocaleDateString('fa-IR')
if (isLoading) {
return <PageLoading />
}
return (
@@ -66,29 +75,67 @@ const Reviews: FC<ReviewsProps> = ({ reviews = [] }) => {
</div>
<div className="mt-4 sm:mt-6 rounded-xl bg-white">
{sortedReviews.length > 0 ? (
sortedReviews.map((review, idx) => (
<div key={review._id} className="px-4 sm:px-6 py-4 sm:py-5">
{sortedComments.length > 0 ? (
sortedComments.map((comment, idx) => (
<div key={comment._id} className="px-4 sm:px-6 py-4 sm:py-5">
<div className="flex flex-col sm:flex-row sm:items-center justify-start gap-2 sm:gap-3">
<div className="px-2 py-0.5 rounded-full bg-[#02A55A] text-white text-xs w-fit">
{review.rating.toFixed(1)}
{comment.rate.toFixed(1)}
</div>
<div className="flex flex-wrap items-center gap-1 sm:gap-2 text-xs text-[#999999]">
{review.isVerified && (
{comment.status === 'approved' && (
<span className="px-2 py-0.5 rounded bg-[#F1F1F1] text-[#7F7F7F]">خریدار</span>
)}
<span className="size-1.5 rounded-full bg-[#E5E5E5]"></span>
<span>{formatDate(review.date)}</span>
<span>{moment(comment.createdAt, 'jYYYY/jMM/jDD HH:mm:ss').format('jYYYY/jMM/jD')}</span>
<span className="size-1.5 rounded-full bg-[#E5E5E5]"></span>
<span>{review.user.name}</span>
<span>{comment.user.fullName}</span>
</div>
</div>
{comment.title && (
<div className="mt-2 text-[#333333] text-sm font-medium text-right">
{comment.title}
</div>
)}
{(comment.advantage && comment.advantage.length > 0) || (comment.disAdvantage && comment.disAdvantage.length > 0) ? (
<div className="mt-4 grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
{comment.advantage && comment.advantage.length > 0 && (
<div className="bg-[#F0F9F4] rounded-lg p-3">
<div className="text-[#02A55A] text-xs sm:text-sm font-medium mb-2">نکات مثبت:</div>
<ul className="text-xs sm:text-sm text-[#02A55A] space-y-1.5">
{comment.advantage.map((item, index) => (
<li key={index} className="flex items-start gap-2 text-right">
<span className="text-[#02A55A] mt-0.5"></span>
<span>{item}</span>
</li>
))}
</ul>
</div>
)}
{comment.disAdvantage && comment.disAdvantage.length > 0 && (
<div className="bg-[#FEF2F2] rounded-lg p-3">
<div className="text-[#EF4444] text-xs sm:text-sm font-medium mb-2">نکات منفی:</div>
<ul className="text-xs sm:text-sm text-[#EF4444] space-y-1.5">
{comment.disAdvantage.map((item, index) => (
<li key={index} className="flex items-start gap-2 text-right">
<span className="text-[#EF4444] mt-0.5"></span>
<span>{item}</span>
</li>
))}
</ul>
</div>
)}
</div>
) : null}
<div className="mt-3 text-[#333333] text-xs sm:text-sm leading-6 sm:leading-7 text-right">
{review.comment}
{comment.content}
</div>
{idx !== sortedReviews.length - 1 && <div className="mt-4 sm:mt-5"> <Separator /> </div>}
{idx !== sortedComments.length - 1 && <div className="mt-4 sm:mt-5"> <Separator /> </div>}
</div>
))
) : (
@@ -125,3 +172,4 @@ const Reviews: FC<ReviewsProps> = ({ reviews = [] }) => {
export default Reviews
+1 -1
View File
@@ -75,7 +75,7 @@ const SingleProduct = ({ id }: SingleProductProps) => {
<Specifications product={product} />
<Description product={product} />
<ProsCons product={product} />
<Reviews product={product} reviews={reviews} />
<Reviews product={product} />
<Questions product={product} questions={questions} />
</div>
</div>
+8
View File
@@ -56,3 +56,11 @@ export const useSaveReport = () => {
mutationFn: api.saveReport,
});
};
export const useGetProductComments = (productId: number) => {
return useQuery({
queryKey: ["product-comments", productId],
queryFn: () => api.getProductComments(productId),
enabled: !!productId,
});
};
+8
View File
@@ -9,6 +9,7 @@ import {
ProductQuestionsResponse,
SaveReportTypes,
} from "../types/Types";
import { CommentsResponse } from "@/app/profile/types/ProfileTypes";
export const getProduct = async (
id: string
@@ -74,3 +75,10 @@ export const saveReport = async (params: SaveReportTypes) => {
);
return data;
};
export const getProductComments = async (
productId: number
): Promise<CommentsResponse> => {
const { data } = await axios.get(`/product/${productId}/comments`);
return data;
};
+2 -3
View File
@@ -225,7 +225,7 @@ export interface CommentCategory {
theme: string;
description: string;
leaf: boolean;
parent: string;
parent: string | null;
}
export interface CommentSpecification {
@@ -254,10 +254,9 @@ export interface CommentProduct {
imagesUrl: CommentImagesUrl;
isFake: string;
voice: string | null;
specifications: CommentSpecification[];
specifications: unknown[];
brand: CommentBrand;
category: CommentCategory;
createdAt: string;
}
export interface Comment {