feat: add interactivity to DashboardPieChart and ChartLegendContent components

This commit is contained in:
Yoga Pangestu 2026-08-15 13:14:25 +07:00
parent fd6793af50
commit e8c46ec18b
3 changed files with 73 additions and 6 deletions

View File

@ -278,9 +278,13 @@ function ChartLegendContent({
payload,
verticalAlign = "bottom",
nameKey,
onItemClick,
activeName,
}: React.ComponentProps<"div"> & {
hideIcon?: boolean
nameKey?: string
onItemClick?: (name: string) => void
activeName?: string
} & RechartsPrimitive.DefaultLegendContentProps) {
const { config } = useChart()
@ -301,13 +305,20 @@ function ChartLegendContent({
.map((item, index) => {
const key = `${nameKey ?? item.dataKey ?? "value"}`
const itemConfig = getPayloadConfigFromPayload(config, item, key)
const label = typeof itemConfig?.label === 'string' ? itemConfig.label : ''
const isInactive = activeName !== undefined && activeName !== label
return (
<div
key={index}
className={cn(
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground",
onItemClick && "cursor-pointer select-none hover:opacity-80",
isInactive && "opacity-40"
)}
onClick={() => {
if (label) onItemClick?.(label)
}}
>
{itemConfig?.icon && !hideIcon ? (
<itemConfig.icon />

View File

@ -267,6 +267,8 @@ type DashboardPieChartProps = {
function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartProps) {
const hasData = data.length > 0 && data.some((d) => d.count > 0);
const [activeName, setActiveName] = useState<string | undefined>(undefined);
const pieColors = useMemo(() => generateRandomColors(5), []);
const chartConfig = useMemo(() => {
@ -287,6 +289,17 @@ function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartP
}));
}, [data, pieColors]);
const handlePieClick = (_: unknown, index: number) => {
const name = chartData[index]?.name;
if (name) {
setActiveName((prev) => (prev === name ? undefined : name));
}
};
const handleLegendClick = (name: string) => {
setActiveName((prev) => (prev === name ? undefined : name));
};
return (
<Card>
<CardHeader className="items-center pb-0">
@ -311,9 +324,24 @@ function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartP
data={chartData}
dataKey={dataKey}
nameKey={nameKey}
/>
onClick={handlePieClick}
>
{chartData.map((item, index) => (
<Cell
key={`cell-${index}`}
opacity={activeName === undefined || activeName === item.name ? 1 : 0.3}
style={{ cursor: 'pointer' }}
/>
))}
</Pie>
<ChartLegend
content={<ChartLegendContent nameKey={nameKey} />}
content={
<ChartLegendContent
nameKey={nameKey}
onItemClick={handleLegendClick}
activeName={activeName}
/>
}
className="-translate-y-2 flex-wrap gap-2 *:basis-1/4 *:justify-center"
/>
</PieChart>

View File

@ -18,7 +18,7 @@ import {
UserCheck,
} from 'lucide-react';
import { useEffect, useMemo, useState } from 'react';
import { Pie, PieChart } from 'recharts';
import { Cell, Pie, PieChart } from 'recharts';
import { toast } from 'sonner';
type TodayAttendance = {
@ -463,6 +463,8 @@ type DashboardPieChartProps = {
function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartProps) {
const hasData = data.length > 0 && data.some((d) => d.count > 0);
const [activeName, setActiveName] = useState<string | undefined>(undefined);
const pieColors = useMemo(() => generateRandomColors(5), []);
const chartConfig = useMemo(() => {
@ -483,6 +485,17 @@ function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartP
}));
}, [data, pieColors]);
const handlePieClick = (_: unknown, index: number) => {
const name = chartData[index]?.name;
if (name) {
setActiveName((prev) => (prev === name ? undefined : name));
}
};
const handleLegendClick = (name: string) => {
setActiveName((prev) => (prev === name ? undefined : name));
};
return (
<Card>
<CardHeader className="items-center pb-0">
@ -507,9 +520,24 @@ function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartP
data={chartData}
dataKey={dataKey}
nameKey={nameKey}
/>
onClick={handlePieClick}
>
{chartData.map((item, index) => (
<Cell
key={`cell-${index}`}
opacity={activeName === undefined || activeName === item.name ? 1 : 0.3}
style={{ cursor: 'pointer' }}
/>
))}
</Pie>
<ChartLegend
content={<ChartLegendContent nameKey={nameKey} />}
content={
<ChartLegendContent
nameKey={nameKey}
onItemClick={handleLegendClick}
activeName={activeName}
/>
}
className="-translate-y-2 flex-wrap gap-2 *:basis-1/4 *:justify-center"
/>
</PieChart>