feat: refactor EventKindsChart to use current user context and update chart colors

This commit is contained in:
2025-12-28 14:24:40 +01:00
parent 098626fbea
commit ecf597d072
3 changed files with 182 additions and 78 deletions

View File

@@ -1,99 +1,191 @@
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
import { ChartContainer, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts';
import { useUserStats } from '@/hooks/useUserStats';
import { Skeleton } from '@/components/ui/skeleton';
"use client"
interface EventKindsChartProps {
pubkey: string;
import { TrendingUp } from "lucide-react"
import { Pie, PieChart } from "recharts"
import { useMemo } from "react"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
type ChartConfig,
} from "@/components/ui/chart"
import { Skeleton } from "@/components/ui/skeleton"
import { useCurrentUser } from "@/hooks/useCurrentUser"
import { useUserStats } from "@/hooks/useUserStats"
export const description = "Distribution of event kinds for the logged-in user"
// Common Nostr event kind labels
const kindLabels: Record<number, string> = {
0: "Metadata",
1: "Note",
3: "Contacts",
4: "DM (Encrypted)",
5: "Delete",
6: "Repost",
7: "Reaction",
16: "Generic Repost",
40: "Channel Create",
41: "Channel Metadata",
42: "Channel Message",
43: "Channel Hide",
44: "Channel Mute",
1984: "Report",
9734: "Zap Request",
9735: "Zap",
10002: "Relay List",
30023: "Long-form",
30311: "Live Event",
}
// Map of common kind numbers to their names
const kindNames: Record<number, string> = {
0: 'Metadata',
1: 'Text Note',
3: 'Contacts',
4: 'DM',
5: 'Deletion',
6: 'Repost',
7: 'Reaction',
9735: 'Zap',
10002: 'Relay List',
30023: 'Article',
};
const chartColors = [
"var(--chart-1)",
"var(--chart-2)",
"var(--chart-3)",
"var(--chart-4)",
"var(--chart-5)",
]
export function EventKindsChart({ pubkey }: EventKindsChartProps) {
const { data: stats, isLoading } = useUserStats(pubkey);
export function EventKindsChart() {
const { user } = useCurrentUser()
const { data: stats, isLoading } = useUserStats(user?.pubkey)
const { chartData, chartConfig, topKind } = useMemo(() => {
if (!stats || !stats.eventsByKind) {
return { chartData: [], chartConfig: {}, topKind: null }
}
// Sort event kinds by count and take top 5
const sortedKinds = Object.entries(stats.eventsByKind)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
// Calculate total for percentage
const total = sortedKinds.reduce((sum, [, count]) => sum + count, 0)
// Generate chart data
const data = sortedKinds.map(([kind, count], index) => ({
kind: `kind${kind}`,
kindNumber: parseInt(kind),
label: kindLabels[parseInt(kind)] || `Kind ${kind}`,
count,
fill: chartColors[index % chartColors.length],
}))
// Generate chart config
const config: ChartConfig = {
count: {
label: "Events",
},
...Object.fromEntries(
data.map((item) => [
item.kind,
{
label: item.label,
color: item.fill,
},
])
),
}
// Find top kind
const top = sortedKinds[0] ? {
kind: parseInt(sortedKinds[0][0]),
count: sortedKinds[0][1],
percentage: total > 0 ? ((sortedKinds[0][1] / total) * 100).toFixed(1) : "0",
} : null
return { chartData: data, chartConfig: config, topKind: top }
}, [stats])
if (isLoading) {
return (
<Card>
<CardHeader>
<Skeleton className="h-6 w-40 mb-2" />
<Skeleton className="h-4 w-64" />
<Card className="flex flex-col">
<CardHeader className="items-center pb-0">
<Skeleton className="h-6 w-32" />
<Skeleton className="h-4 w-48 mt-2" />
</CardHeader>
<CardContent>
<Skeleton className="h-[300px] w-full" />
</CardContent>
</Card>
);
}
if (!stats || Object.keys(stats.eventsByKind).length === 0) {
return (
<Card>
<CardHeader>
<CardTitle>Event Distribution</CardTitle>
<CardDescription>Events published by kind</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center justify-center h-[300px] text-muted-foreground">
No events found
<CardContent className="flex-1 pb-0">
<div className="mx-auto aspect-square max-h-[250px] flex items-center justify-center">
<Skeleton className="h-[250px] w-[250px] rounded-full" />
</div>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm">
<Skeleton className="h-4 w-48" />
<Skeleton className="h-4 w-56" />
</CardFooter>
</Card>
);
)
}
// Transform data for chart
const chartData = Object.entries(stats.eventsByKind)
.sort(([, a], [, b]) => b - a)
.slice(0, 10) // Top 10 kinds
.map(([kind, count]) => ({
kind: kindNames[Number(kind)] || `Kind ${kind}`,
count,
}));
if (!user) {
return (
<Card className="flex flex-col">
<CardHeader className="items-center pb-0">
<CardTitle>Event Kinds Distribution</CardTitle>
<CardDescription>Log in to see your event distribution</CardDescription>
</CardHeader>
<CardContent className="flex-1 pb-0 flex items-center justify-center min-h-[250px]">
<p className="text-muted-foreground text-sm">Please log in to view your stats</p>
</CardContent>
</Card>
)
}
const chartConfig = {
count: {
label: 'Events',
color: 'hsl(var(--chart-1))',
},
};
if (!stats || chartData.length === 0) {
return (
<Card className="flex flex-col">
<CardHeader className="items-center pb-0">
<CardTitle>Event Kinds Distribution</CardTitle>
<CardDescription>Your published event types</CardDescription>
</CardHeader>
<CardContent className="flex-1 pb-0 flex items-center justify-center min-h-[250px]">
<p className="text-muted-foreground text-sm">No events found</p>
</CardContent>
</Card>
)
}
return (
<Card>
<CardHeader>
<CardTitle>Event Distribution</CardTitle>
<CardDescription>Top event types you've published</CardDescription>
<Card className="flex flex-col">
<CardHeader className="items-center pb-0">
<CardTitle>Event Kinds Distribution</CardTitle>
<CardDescription>Your top 5 event types</CardDescription>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig} className="h-[300px] w-full">
<BarChart data={chartData}>
<CartesianGrid strokeDasharray="3 3" className="stroke-muted" />
<XAxis
dataKey="kind"
angle={-45}
textAnchor="end"
height={80}
className="text-xs"
<CardContent className="flex-1 pb-0">
<ChartContainer
config={chartConfig}
className="mx-auto aspect-square max-h-[250px]"
>
<PieChart>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<YAxis className="text-xs" />
<ChartTooltip content={<ChartTooltipContent />} />
<Bar dataKey="count" fill="var(--color-count)" radius={[4, 4, 0, 0]} />
</BarChart>
<Pie data={chartData} dataKey="count" nameKey="label" />
</PieChart>
</ChartContainer>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm">
{topKind && (
<div className="flex items-center gap-2 leading-none font-medium">
{kindLabels[topKind.kind] || `Kind ${topKind.kind}`} is your most common event ({topKind.percentage}%)
<TrendingUp className="h-4 w-4" />
</div>
)}
<div className="text-muted-foreground leading-none">
Showing distribution of your last {stats.totalEvents} events
</div>
</CardFooter>
</Card>
);
)
}

View File

@@ -49,6 +49,12 @@
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
}
.dark {
@@ -87,6 +93,12 @@
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
}
}

View File

@@ -49,7 +49,7 @@ export function Dashboard() {
<DashboardStats pubkey={user.pubkey} />
<div className="grid gap-6 md:grid-cols-2">
<EventKindsChart pubkey={user.pubkey} />
<EventKindsChart />
<RecentActivityList pubkey={user.pubkey} />
</div>
</>