diff --git a/components/Admin/AdminPieChart.tsx b/components/Admin/AdminPieChart.tsx new file mode 100644 index 0000000..8ec3d4f --- /dev/null +++ b/components/Admin/AdminPieChart.tsx @@ -0,0 +1,74 @@ +"use client"; + +import { Pie, PieChart, Cell } from "recharts"; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + ChartLegend, + ChartLegendContent, + type ChartConfig, +} from "@/components/ui/chart"; + +const COLORS = [ + "#345A5D", + "#6B8E93", + "#4A90A4", + "#8FBCC4", + "#2A4648", + "#5BA3B5", + "#3D7A7E", + "#97C4CC", +]; + +export interface PieChartSlice { + name: string; + value: number; +} + +interface AdminPieChartProps { + title: string; + data: PieChartSlice[]; +} + +export default function AdminPieChart({ title, data }: AdminPieChartProps) { + if (data.length === 0) return null; + + const chartConfig = Object.fromEntries( + data.map((slice, i) => [ + slice.name, + { label: slice.name, color: COLORS[i % COLORS.length] }, + ]), + ) satisfies ChartConfig; + + return ( +
+

+ {title} +

+ + + } /> + + {data.map((slice, i) => ( + + ))} + + } /> + + +
+ ); +} diff --git a/components/ContentSlack/ContentSlackPage.tsx b/components/ContentSlack/ContentSlackPage.tsx index 07e251d..9d18ab0 100644 --- a/components/ContentSlack/ContentSlackPage.tsx +++ b/components/ContentSlack/ContentSlackPage.tsx @@ -8,9 +8,12 @@ import ContentSlackTable from "@/components/ContentSlack/ContentSlackTable"; import ContentSlackStats from "@/components/ContentSlack/ContentSlackStats"; import PeriodSelector from "@/components/Admin/PeriodSelector"; import AdminLineChart from "@/components/Admin/AdminLineChart"; +import AdminPieChart from "@/components/Admin/AdminPieChart"; import TableSkeleton from "@/components/Sandboxes/TableSkeleton"; import ChartSkeleton from "@/components/PrivyLogins/ChartSkeleton"; +import PieChartSkeleton from "@/components/ContentSlack/PieChartSkeleton"; import { getTagsByDate } from "@/lib/coding-agent/getTagsByDate"; +import { getTagsByUser } from "@/lib/contentSlack/getTagsByUser"; import type { AdminPeriod } from "@/types/admin"; export default function ContentSlackPage() { @@ -25,6 +28,7 @@ export default function ContentSlackPage() { })), ) : []; + const tagsByUser = data ? getTagsByUser(data.tags) : []; return (
@@ -49,6 +53,7 @@ export default function ContentSlackPage() { {isLoading && ( <> + )} @@ -76,6 +81,9 @@ export default function ContentSlackPage() { label: "Tags with Videos", }} /> +
+ +
)} diff --git a/components/ContentSlack/PieChartSkeleton.tsx b/components/ContentSlack/PieChartSkeleton.tsx new file mode 100644 index 0000000..ed71bce --- /dev/null +++ b/components/ContentSlack/PieChartSkeleton.tsx @@ -0,0 +1,10 @@ +export default function PieChartSkeleton() { + return ( +
+
+
+
+
+
+ ); +} diff --git a/lib/contentSlack/getTagsByUser.ts b/lib/contentSlack/getTagsByUser.ts new file mode 100644 index 0000000..ed3ae26 --- /dev/null +++ b/lib/contentSlack/getTagsByUser.ts @@ -0,0 +1,19 @@ +import type { ContentSlackTag } from "@/types/contentSlack"; +import type { PieChartSlice } from "@/components/Admin/AdminPieChart"; + +/** + * Aggregates content slack tags by user_name for pie chart display. + * Returns slices sorted descending by count. + */ +export function getTagsByUser(tags: ContentSlackTag[]): PieChartSlice[] { + const counts = new Map(); + + for (const tag of tags) { + const name = tag.user_name || "Unknown"; + counts.set(name, (counts.get(name) ?? 0) + 1); + } + + return Array.from(counts.entries()) + .map(([name, value]) => ({ name, value })) + .sort((a, b) => b.value - a.value); +}