comments
This commit is contained in:
@@ -123,7 +123,48 @@ function AboutPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatDate = (dateString: string): string => {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toLocaleDateString('fa-IR', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const secondTab = () => {
|
const secondTab = () => {
|
||||||
|
const reviews = (reviewsData?.data || []).filter(review => review.isApproved);
|
||||||
|
|
||||||
|
const sortedReviews = [...reviews].sort((a, b) => {
|
||||||
|
if (sorting === '0') {
|
||||||
|
// جدیدترین
|
||||||
|
return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime();
|
||||||
|
} else {
|
||||||
|
// قدیمی ترین
|
||||||
|
return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const averageRating = reviews.length > 0
|
||||||
|
? reviews.reduce((sum, review) => sum + review.rating, 0) / reviews.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const ratingCounts = {
|
||||||
|
5: reviews.filter(r => r.rating === 5).length,
|
||||||
|
4: reviews.filter(r => r.rating === 4).length,
|
||||||
|
3: reviews.filter(r => r.rating === 3).length,
|
||||||
|
2: reviews.filter(r => r.rating === 2).length,
|
||||||
|
1: reviews.filter(r => r.rating === 1).length,
|
||||||
|
};
|
||||||
|
|
||||||
|
const totalReviews = reviews.length;
|
||||||
|
const ratingPercentages = {
|
||||||
|
5: totalReviews > 0 ? Math.round((ratingCounts[5] / totalReviews) * 100) : 0,
|
||||||
|
4: totalReviews > 0 ? Math.round((ratingCounts[4] / totalReviews) * 100) : 0,
|
||||||
|
3: totalReviews > 0 ? Math.round((ratingCounts[3] / totalReviews) * 100) : 0,
|
||||||
|
2: totalReviews > 0 ? Math.round((ratingCounts[2] / totalReviews) * 100) : 0,
|
||||||
|
1: totalReviews > 0 ? Math.round((ratingCounts[1] / totalReviews) * 100) : 0,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section aria-labelledby="reviews-title" className='py-4'>
|
<section aria-labelledby="reviews-title" className='py-4'>
|
||||||
@@ -131,14 +172,14 @@ function AboutPage() {
|
|||||||
aria-label='امتیاز کاربران'
|
aria-label='امتیاز کاربران'
|
||||||
className="bg-container rounded-container shadow-container p-4 py-6 grid grid-cols-2 items-center">
|
className="bg-container rounded-container shadow-container p-4 py-6 grid grid-cols-2 items-center">
|
||||||
<div className="text-center font-bold text-5xl">
|
<div className="text-center font-bold text-5xl">
|
||||||
4.1
|
{averageRating.toFixed(1)}
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
<div className="flex flex-col w-fit items-end">
|
||||||
<RateBar className='mt-1' content='5' percentage='55' />
|
<RateBar className='mt-1' content='5' percentage={String(ratingPercentages[5])} />
|
||||||
<RateBar className='mt-1' content='4' percentage='32' />
|
<RateBar className='mt-1' content='4' percentage={String(ratingPercentages[4])} />
|
||||||
<RateBar className='mt-1' content='3' percentage='70' />
|
<RateBar className='mt-1' content='3' percentage={String(ratingPercentages[3])} />
|
||||||
<RateBar className='mt-1' content='2' percentage='12' />
|
<RateBar className='mt-1' content='2' percentage={String(ratingPercentages[2])} />
|
||||||
<RateBar className='mt-1' content='1' percentage='89' />
|
<RateBar className='mt-1' content='1' percentage={String(ratingPercentages[1])} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -151,18 +192,24 @@ function AboutPage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="">
|
<div className="">
|
||||||
|
{sortedReviews.length > 0 ? (
|
||||||
<article>
|
sortedReviews.map((review) => (
|
||||||
|
<article key={review.id}>
|
||||||
<Comment
|
<Comment
|
||||||
className='pt-8'
|
className='pt-8'
|
||||||
user='علیرضا عابدزاده'
|
user={review.user?.firstName + ' ' + review.user?.lastName}
|
||||||
rating={5.0}
|
rating={review.rating}
|
||||||
date='18 آذر 1401'
|
date={formatDate(review.createdAt)}
|
||||||
text='لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است'
|
text={review.comment}
|
||||||
tags={['پیتزا قارچ مخصوص', 'چیز برگر ژیوان']}
|
tags={[review.food.title]}
|
||||||
|
positivePoints={review.positivePoints}
|
||||||
|
negativePoints={review.negativePoints}
|
||||||
/>
|
/>
|
||||||
</article>
|
</article>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className='text-sm2 text-center py-8 text-disabled-text'>هنوز نظری ثبت نشده است</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<AnimatedBottomSheet title="مرتب کردن بر اساس" visible={sortingModal} inDelay={150} onClick={toggleSortingModal}>
|
<AnimatedBottomSheet title="مرتب کردن بر اساس" visible={sortingModal} inDelay={150} onClick={toggleSortingModal}>
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { api } from "@/config/axios";
|
import { api } from "@/config/axios";
|
||||||
import { AboutResponse } from "../types/Types";
|
import { AboutResponse, ReviewsResponse } from "../types/Types";
|
||||||
|
|
||||||
export const getAbout = async (name: string): Promise<AboutResponse> => {
|
export const getAbout = async (name: string): Promise<AboutResponse> => {
|
||||||
const { data } = await api.get<AboutResponse>(`/public/restaurants/${name}`);
|
const { data } = await api.get<AboutResponse>(`/public/restaurants/${name}`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getReviews = async (name: string) => {
|
export const getReviews = async (name: string): Promise<ReviewsResponse> => {
|
||||||
const { data } = await api.get(`/public/reviews/restuarant/${name}`);
|
const { data } = await api.get<ReviewsResponse>(
|
||||||
|
`/public/reviews/restuarant/${name}`
|
||||||
|
);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,4 +29,68 @@ export interface Restaurant {
|
|||||||
vat: number;
|
vat: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReviewFood {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
restaurant: string;
|
||||||
|
category: string;
|
||||||
|
title: string;
|
||||||
|
desc: string | null;
|
||||||
|
content: string | null;
|
||||||
|
price: number;
|
||||||
|
points: number | null;
|
||||||
|
order: number | null;
|
||||||
|
prepareTime: number | null;
|
||||||
|
sat: boolean;
|
||||||
|
sun: boolean;
|
||||||
|
mon: boolean;
|
||||||
|
breakfast: boolean;
|
||||||
|
noon: boolean;
|
||||||
|
dinner: boolean;
|
||||||
|
stock: number;
|
||||||
|
stockDefault: number;
|
||||||
|
isActive: boolean;
|
||||||
|
images: string[];
|
||||||
|
inPlaceServe: boolean;
|
||||||
|
pickupServe: boolean;
|
||||||
|
rate: number;
|
||||||
|
discount: number;
|
||||||
|
isSpecialOffer: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReviewUser {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
birthDate: string;
|
||||||
|
marriageDate: string;
|
||||||
|
referrer: string | null;
|
||||||
|
isActive: boolean;
|
||||||
|
gender: boolean;
|
||||||
|
wallet: number;
|
||||||
|
points: number;
|
||||||
|
phone: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Review {
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
deletedAt: string | null;
|
||||||
|
order: string;
|
||||||
|
food: ReviewFood;
|
||||||
|
user: ReviewUser;
|
||||||
|
comment: string;
|
||||||
|
rating: number;
|
||||||
|
positivePoints: string[];
|
||||||
|
negativePoints: string[];
|
||||||
|
isApproved: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export type AboutResponse = BaseResponse<Restaurant>;
|
export type AboutResponse = BaseResponse<Restaurant>;
|
||||||
|
export type ReviewsResponse = BaseResponse<Review[]>;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { Star1 } from 'iconsax-react'
|
import { Star1 } from 'iconsax-react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: string;
|
user: string;
|
||||||
@@ -9,10 +10,19 @@ type Props = {
|
|||||||
date: string;
|
date: string;
|
||||||
text: string;
|
text: string;
|
||||||
tags: Array<string>;
|
tags: Array<string>;
|
||||||
|
positivePoints?: string[];
|
||||||
|
negativePoints?: string[];
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Comment({ user, rating, date, text, tags, className = '' }: Props) {
|
function Comment({ user, rating, date, text, tags, positivePoints = [], negativePoints = [], className = '' }: Props) {
|
||||||
|
const { t } = useTranslation("rating");
|
||||||
|
|
||||||
|
const getTranslationKey = (point: string, isPositive: boolean): string => {
|
||||||
|
const basePath = isPositive ? 'Tabs.Strengths.Options' : 'Tabs.Weeknesses.Options';
|
||||||
|
return `${basePath}.${point}`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<div className="flex items-center justify-start">
|
<div className="flex items-center justify-start">
|
||||||
@@ -29,6 +39,7 @@ function Comment({ user, rating, date, text, tags, className = '' }: Props) {
|
|||||||
{text}
|
{text}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{tags.length > 0 && (
|
||||||
<ul className='mt-3 flex flex-wrap items-center justify-start gap-2'>
|
<ul className='mt-3 flex flex-wrap items-center justify-start gap-2'>
|
||||||
{tags.map((v, i) => (
|
{tags.map((v, i) => (
|
||||||
<li key={i} className='text-xs py-1.5 px-3 bg-[#F2F2F2] dark:bg-neutral-700 dark:text-disabled-text rounded-sm text-center'>
|
<li key={i} className='text-xs py-1.5 px-3 bg-[#F2F2F2] dark:bg-neutral-700 dark:text-disabled-text rounded-sm text-center'>
|
||||||
@@ -36,6 +47,22 @@ function Comment({ user, rating, date, text, tags, className = '' }: Props) {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(positivePoints.length > 0 || negativePoints.length > 0) && (
|
||||||
|
<ul className='mt-5 flex flex-wrap items-center justify-start gap-2'>
|
||||||
|
{positivePoints.map((point, i) => (
|
||||||
|
<li key={`positive-${i}`} className='text-xs py-1.5 px-3 bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400 rounded-sm text-center'>
|
||||||
|
{t(getTranslationKey(point, true))}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{negativePoints.map((point, i) => (
|
||||||
|
<li key={`negative-${i}`} className='text-xs py-1.5 px-3 bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 rounded-sm text-center'>
|
||||||
|
{t(getTranslationKey(point, false))}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user