Files
reui-blocks/blocks/reviews4.tsx
T

240 lines
7.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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 };