feat: add interactivity to DashboardPieChart and ChartLegendContent components
This commit is contained in:
parent
fd6793af50
commit
e8c46ec18b
@ -278,9 +278,13 @@ function ChartLegendContent({
|
|||||||
payload,
|
payload,
|
||||||
verticalAlign = "bottom",
|
verticalAlign = "bottom",
|
||||||
nameKey,
|
nameKey,
|
||||||
|
onItemClick,
|
||||||
|
activeName,
|
||||||
}: React.ComponentProps<"div"> & {
|
}: React.ComponentProps<"div"> & {
|
||||||
hideIcon?: boolean
|
hideIcon?: boolean
|
||||||
nameKey?: string
|
nameKey?: string
|
||||||
|
onItemClick?: (name: string) => void
|
||||||
|
activeName?: string
|
||||||
} & RechartsPrimitive.DefaultLegendContentProps) {
|
} & RechartsPrimitive.DefaultLegendContentProps) {
|
||||||
const { config } = useChart()
|
const { config } = useChart()
|
||||||
|
|
||||||
@ -301,13 +305,20 @@ function ChartLegendContent({
|
|||||||
.map((item, index) => {
|
.map((item, index) => {
|
||||||
const key = `${nameKey ?? item.dataKey ?? "value"}`
|
const key = `${nameKey ?? item.dataKey ?? "value"}`
|
||||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
const label = typeof itemConfig?.label === 'string' ? itemConfig.label : ''
|
||||||
|
const isInactive = activeName !== undefined && activeName !== label
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className={cn(
|
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 && !hideIcon ? (
|
||||||
<itemConfig.icon />
|
<itemConfig.icon />
|
||||||
|
|||||||
@ -267,6 +267,8 @@ type DashboardPieChartProps = {
|
|||||||
function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartProps) {
|
function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartProps) {
|
||||||
const hasData = data.length > 0 && data.some((d) => d.count > 0);
|
const hasData = data.length > 0 && data.some((d) => d.count > 0);
|
||||||
|
|
||||||
|
const [activeName, setActiveName] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
const pieColors = useMemo(() => generateRandomColors(5), []);
|
const pieColors = useMemo(() => generateRandomColors(5), []);
|
||||||
|
|
||||||
const chartConfig = useMemo(() => {
|
const chartConfig = useMemo(() => {
|
||||||
@ -287,6 +289,17 @@ function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartP
|
|||||||
}));
|
}));
|
||||||
}, [data, pieColors]);
|
}, [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 (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="items-center pb-0">
|
<CardHeader className="items-center pb-0">
|
||||||
@ -311,9 +324,24 @@ function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartP
|
|||||||
data={chartData}
|
data={chartData}
|
||||||
dataKey={dataKey}
|
dataKey={dataKey}
|
||||||
nameKey={nameKey}
|
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
|
<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"
|
className="-translate-y-2 flex-wrap gap-2 *:basis-1/4 *:justify-center"
|
||||||
/>
|
/>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import {
|
|||||||
UserCheck,
|
UserCheck,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { Pie, PieChart } from 'recharts';
|
import { Cell, Pie, PieChart } from 'recharts';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
type TodayAttendance = {
|
type TodayAttendance = {
|
||||||
@ -463,6 +463,8 @@ type DashboardPieChartProps = {
|
|||||||
function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartProps) {
|
function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartProps) {
|
||||||
const hasData = data.length > 0 && data.some((d) => d.count > 0);
|
const hasData = data.length > 0 && data.some((d) => d.count > 0);
|
||||||
|
|
||||||
|
const [activeName, setActiveName] = useState<string | undefined>(undefined);
|
||||||
|
|
||||||
const pieColors = useMemo(() => generateRandomColors(5), []);
|
const pieColors = useMemo(() => generateRandomColors(5), []);
|
||||||
|
|
||||||
const chartConfig = useMemo(() => {
|
const chartConfig = useMemo(() => {
|
||||||
@ -483,6 +485,17 @@ function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartP
|
|||||||
}));
|
}));
|
||||||
}, [data, pieColors]);
|
}, [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 (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="items-center pb-0">
|
<CardHeader className="items-center pb-0">
|
||||||
@ -507,9 +520,24 @@ function DashboardPieChart({ title, data, dataKey, nameKey }: DashboardPieChartP
|
|||||||
data={chartData}
|
data={chartData}
|
||||||
dataKey={dataKey}
|
dataKey={dataKey}
|
||||||
nameKey={nameKey}
|
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
|
<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"
|
className="-translate-y-2 flex-wrap gap-2 *:basis-1/4 *:justify-center"
|
||||||
/>
|
/>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user