mirror of
https://github.com/layer-systems/website.git
synced 2026-06-17 01:58:30 +02:00
feat: refactor EventKindsChart to use current user context and update chart colors
This commit is contained in:
@@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user