240 lines
7.9 KiB
TypeScript
240 lines
7.9 KiB
TypeScript
"use client";
|
||
|
||
import { Star } from "lucide-react";
|
||
import { useState } from "react";
|
||
|
||
import { Rating } from "@/components/shadcnblocks/rating";
|
||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||
import { Button } from "@/components/ui/button";
|
||
import { Progress } from "@/components/ui/progress";
|
||
import { cn } from "@/lib/utils";
|
||
|
||
interface Review {
|
||
id: string;
|
||
rating: number;
|
||
content: string;
|
||
author: {
|
||
name: string;
|
||
avatar?: string;
|
||
};
|
||
date: string;
|
||
}
|
||
|
||
const DEFAULT_REVIEWS: Review[] = [
|
||
{
|
||
id: "1",
|
||
rating: 5,
|
||
content:
|
||
"واقعاً این محصول را دوست دارم! کیفیتش از انتظارم بهتر بود و زود به دستم رسید. حتماً دوباره میخرم.",
|
||
author: {
|
||
name: "سارا احمدی",
|
||
avatar: "/block-media/shadcnblocks/block/avatar-1.webp",
|
||
},
|
||
date: "۲۰ آذر ۱۴۰۳",
|
||
},
|
||
{
|
||
id: "2",
|
||
rating: 5,
|
||
content:
|
||
"عالی! دقیقاً همان چیزی بود که دنبالش بودم. با این قیمت، ارزش خریدش بالاست.",
|
||
author: {
|
||
name: "مازیار رضایی",
|
||
avatar: "/block-media/shadcnblocks/block/avatar-2.webp",
|
||
},
|
||
date: "۱۸ آذر ۱۴۰۳",
|
||
},
|
||
{
|
||
id: "3",
|
||
rating: 4,
|
||
content:
|
||
"بهطور کلی کیفیت خوبی دارد. یک ستاره کم کردم چون ارسال کمی بیشتر از موعد طول کشید، ولی خود محصول عالی است.",
|
||
author: {
|
||
name: "نیلوفر حسینی",
|
||
avatar: "/block-media/shadcnblocks/block/avatar-3.webp",
|
||
},
|
||
date: "۱۵ آذر ۱۴۰۳",
|
||
},
|
||
{
|
||
id: "4",
|
||
rating: 5,
|
||
content:
|
||
"برای سومین بار سفارش دادم. کیفیت همیشه ثابت است و وقتی سوال داشتم، پشتیبانی سریع جواب داد.",
|
||
author: {
|
||
name: "امیر محمدی",
|
||
},
|
||
date: "۱۲ آذر ۱۴۰۳",
|
||
},
|
||
{
|
||
id: "5",
|
||
rating: 4,
|
||
content:
|
||
"محصول خوبی است و همانطور که توضیح داده شده کار میکند. به دوستان و خانواده پیشنهاد میکنم.",
|
||
author: {
|
||
name: "رؤیا کاظمی",
|
||
avatar: "/block-media/shadcnblocks/block/avatar-7.webp",
|
||
},
|
||
date: "۸ آذر ۱۴۰۳",
|
||
},
|
||
{
|
||
id: "6",
|
||
rating: 5,
|
||
content:
|
||
"وای! به جزئیات با دقت زیادی توجه شده. معلوم است با وسواس ساخته شده.",
|
||
author: {
|
||
name: "کاوه مرادی",
|
||
avatar: "/block-media/shadcnblocks/block/avatar-4.webp",
|
||
},
|
||
date: "۵ آذر ۱۴۰۳",
|
||
},
|
||
];
|
||
|
||
interface Reviews4Props {
|
||
reviews?: Review[];
|
||
className?: string;
|
||
}
|
||
|
||
const Reviews4 = ({ reviews = DEFAULT_REVIEWS, className }: Reviews4Props) => {
|
||
const [selectedRating, setSelectedRating] = useState<number | null>(null);
|
||
|
||
const totalReviews = reviews.length;
|
||
const averageRating =
|
||
reviews.reduce((sum, review) => sum + review.rating, 0) / totalReviews;
|
||
|
||
const formattedAverage = averageRating.toLocaleString("fa-IR", {
|
||
minimumFractionDigits: 1,
|
||
maximumFractionDigits: 1,
|
||
});
|
||
|
||
const ratingCounts = [5, 4, 3, 2, 1].map((star) => ({
|
||
star,
|
||
count: reviews.filter((r) => r.rating === star).length,
|
||
percentage:
|
||
(reviews.filter((r) => r.rating === star).length / totalReviews) * 100,
|
||
}));
|
||
|
||
const filteredReviews = selectedRating
|
||
? reviews.filter((r) => r.rating === selectedRating)
|
||
: reviews;
|
||
|
||
const selectedStarLabel =
|
||
selectedRating?.toLocaleString("fa-IR") ?? "";
|
||
|
||
return (
|
||
<section
|
||
dir="rtl"
|
||
lang="fa"
|
||
className={cn("py-16 md:py-24", className)}
|
||
>
|
||
<div className="container max-w-4xl">
|
||
<div className="mb-8 rounded-xl border bg-muted/30 p-6 md:p-8">
|
||
<div className="flex flex-col gap-6 md:flex-row md:items-center md:gap-12">
|
||
<div className="text-center md:text-start">
|
||
<div className="text-2xll font-bold tracking-tight">
|
||
{formattedAverage}
|
||
</div>
|
||
<Rating
|
||
rate={averageRating}
|
||
className="mt-2 justify-center md:justify-start [&_svg]:size-5"
|
||
/>
|
||
<p className="mt-2 text-sm text-muted-foreground">
|
||
{totalReviews.toLocaleString("fa-IR")} نظر
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex-1 space-y-2">
|
||
{ratingCounts.map(({ star, count, percentage }) => (
|
||
<button
|
||
key={star}
|
||
type="button"
|
||
onClick={() =>
|
||
setSelectedRating(selectedRating === star ? null : star)
|
||
}
|
||
className={cn(
|
||
"flex w-full items-center gap-3 rounded-md px-2 py-1 transition-colors hover:bg-muted",
|
||
selectedRating === star && "bg-muted",
|
||
)}
|
||
aria-pressed={selectedRating === star}
|
||
aria-label={`فیلتر نظرات ${star.toLocaleString("fa-IR")} ستاره، ${count.toLocaleString("fa-IR")} مورد`}
|
||
>
|
||
<div className="flex w-12 shrink-0 items-center gap-1 text-sm">
|
||
<span>{star.toLocaleString("fa-IR")}</span>
|
||
<Star className="size-3.5 fill-current" />
|
||
</div>
|
||
<Progress value={percentage} className="h-2 flex-1" />
|
||
<span className="w-8 shrink-0 text-end text-sm text-muted-foreground">
|
||
{count.toLocaleString("fa-IR")}
|
||
</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{selectedRating && (
|
||
<div className="mb-4 flex items-center gap-2">
|
||
<span className="text-sm text-muted-foreground">
|
||
نمایش نظرات {selectedStarLabel} ستاره
|
||
</span>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="h-auto p-1 text-xs"
|
||
onClick={() => setSelectedRating(null)}
|
||
>
|
||
حذف فیلتر
|
||
</Button>
|
||
</div>
|
||
)}
|
||
|
||
<div className="grid gap-4 sm:grid-cols-2">
|
||
{filteredReviews.map((review) => (
|
||
<div
|
||
key={review.id}
|
||
className="rounded-lg border bg-card p-4 transition-shadow hover:shadow-sm"
|
||
>
|
||
<div className="flex items-start justify-between gap-3">
|
||
<div className="flex items-center gap-2.5">
|
||
<Avatar className="size-8">
|
||
<AvatarImage
|
||
src={review.author.avatar}
|
||
alt={review.author.name}
|
||
/>
|
||
<AvatarFallback className="text-xs">
|
||
{review.author.name
|
||
.split(/\s+/)
|
||
.slice(0, 2)
|
||
.map((part) => part[0])
|
||
.join("")}
|
||
</AvatarFallback>
|
||
</Avatar>
|
||
<div>
|
||
<p className="text-sm font-medium">{review.author.name}</p>
|
||
<p className="text-xs text-muted-foreground">
|
||
{review.date}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<Rating
|
||
rate={review.rating}
|
||
className="[&_svg]:size-3 [&>div]:size-3"
|
||
/>
|
||
</div>
|
||
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
|
||
{review.content}
|
||
</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{filteredReviews.length === 0 && selectedRating && (
|
||
<p className="py-8 text-center text-muted-foreground">
|
||
هنوز نظری با {selectedStarLabel} ستاره ثبت نشده است.
|
||
</p>
|
||
)}
|
||
</div>
|
||
</section>
|
||
);
|
||
};
|
||
|
||
export { Reviews4 };
|