172 lines
5.1 KiB
TypeScript
172 lines
5.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Cell, Pie, PieChart, ResponsiveContainer, Sector } from "recharts";
|
|
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from "@/components/ui/card";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
interface DataItem {
|
|
name: string;
|
|
value: number;
|
|
color: string;
|
|
}
|
|
|
|
interface ChartCard5Props {
|
|
title?: string;
|
|
description?: string;
|
|
centerValue?: string;
|
|
centerLabel?: string;
|
|
data?: DataItem[];
|
|
className?: string;
|
|
}
|
|
|
|
const numberFormatter = new Intl.NumberFormat("fa-IR", {
|
|
maximumFractionDigits: 0,
|
|
});
|
|
|
|
const defaultData: DataItem[] = [
|
|
{ name: "دسکتاپ", value: 4520, color: "var(--chart-1)" },
|
|
{ name: "موبایل", value: 3210, color: "var(--chart-2)" },
|
|
{ name: "تبلت", value: 1240, color: "var(--chart-3)" },
|
|
];
|
|
|
|
const defaultTotalVisitors = defaultData.reduce(
|
|
(sum, item) => sum + item.value,
|
|
0,
|
|
);
|
|
|
|
interface ActiveShapeProps {
|
|
cx: number;
|
|
cy: number;
|
|
innerRadius: number;
|
|
outerRadius: number;
|
|
startAngle: number;
|
|
endAngle: number;
|
|
fill: string;
|
|
}
|
|
|
|
const renderActiveShape = (props: ActiveShapeProps) => {
|
|
const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill } =
|
|
props;
|
|
|
|
return (
|
|
<Sector
|
|
cx={cx}
|
|
cy={cy}
|
|
innerRadius={innerRadius - 2}
|
|
outerRadius={outerRadius + 4}
|
|
startAngle={startAngle}
|
|
endAngle={endAngle}
|
|
fill={fill}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const ChartCard5 = ({
|
|
title = "ترافیک به تفکیک دستگاه",
|
|
description = "تفکیک بازدیدکنندگان بر اساس نوع دستگاه",
|
|
centerValue = numberFormatter.format(defaultTotalVisitors),
|
|
centerLabel = "مجموع بازدیدکنندگان",
|
|
data = defaultData,
|
|
className,
|
|
}: ChartCard5Props) => {
|
|
const [activeIndex, setActiveIndex] = useState<number | null>(null);
|
|
|
|
return (
|
|
<Card
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn("w-full max-w-2xl text-start", className)}
|
|
>
|
|
<CardHeader>
|
|
<CardTitle>{title}</CardTitle>
|
|
<CardDescription>{description}</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center justify-center gap-8">
|
|
<div className="relative size-40">
|
|
<ResponsiveContainer width="100%" height="100%">
|
|
<PieChart>
|
|
<Pie
|
|
data={data}
|
|
cx="50%"
|
|
cy="50%"
|
|
innerRadius={50}
|
|
outerRadius={70}
|
|
paddingAngle={2}
|
|
dataKey="value"
|
|
strokeWidth={0}
|
|
// activeIndex={activeIndex ?? undefined}
|
|
// activeShape={renderActiveShape}
|
|
onMouseEnter={(_, index) => setActiveIndex(index)}
|
|
onMouseLeave={() => setActiveIndex(null)}
|
|
>
|
|
{data.map((entry, index) => (
|
|
<Cell
|
|
key={index}
|
|
fill={entry.color}
|
|
style={{
|
|
cursor: "pointer",
|
|
transition: "opacity 0.4s ease-out",
|
|
opacity:
|
|
activeIndex === null || activeIndex === index
|
|
? 1
|
|
: 0.3,
|
|
}}
|
|
/>
|
|
))}
|
|
</Pie>
|
|
</PieChart>
|
|
</ResponsiveContainer>
|
|
<div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center">
|
|
<span className="text-xl font-bold tabular-nums">
|
|
{centerValue}
|
|
</span>
|
|
<span className="text-xs text-muted-foreground">
|
|
{centerLabel}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 space-y-3">
|
|
{data.map((item, index) => (
|
|
<div
|
|
key={index}
|
|
className={cn(
|
|
"flex cursor-pointer items-center justify-between rounded-md px-2 py-1.5 transition-all duration-300 ease-out",
|
|
activeIndex === index ? "bg-muted" : "hover:bg-muted/50",
|
|
activeIndex !== null && activeIndex !== index && "opacity-40",
|
|
)}
|
|
onMouseEnter={() => setActiveIndex(index)}
|
|
onMouseLeave={() => setActiveIndex(null)}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<div
|
|
className={cn(
|
|
"size-3 rounded-full transition-transform duration-300 ease-out",
|
|
activeIndex === index && "scale-125",
|
|
)}
|
|
style={{ backgroundColor: item.color }}
|
|
/>
|
|
<span className="text-sm">{item.name}</span>
|
|
</div>
|
|
<span className="text-sm font-medium tabular-nums">
|
|
{numberFormatter.format(item.value)}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export { ChartCard5 };
|