commit ae190fad46e1af5baa05ef955e8c3a1e4b116a15
parent 68f6fd41bdefabffcc8e851a9177ba43f0675682
Author: MTRNord <mtrnord1@gmail.com>
Date: Fri, 25 Apr 2025 20:22:57 +0200
Fix barchart and move analytics board to the right folder
Signed-off-by: MTRNord <mtrnord1@gmail.com>
Diffstat:
4 files changed, 434 insertions(+), 390 deletions(-)
diff --git a/src/app/dashboard/analytics-dashboard.tsx b/src/app/dashboard/analytics-dashboard.tsx
@@ -1,358 +0,0 @@
-import { useState } from "react"
-import { TrendingUp } from "lucide-react"
-
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
-import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-
-import { PieChart } from "@/components/analytics/pie-chart"
-import { Heatmap } from "@/components/analytics/heatmap"
-import { BarChart } from "@/components/analytics/bar-chart"
-import { HorizontalBarChart } from "@/components/analytics/horizontal-bar-chart"
-
-interface InfoCardWithTrendProps {
- title: string;
- changePercent: number;
- value: string | number;
- trendColorOverride?: string;
-}
-
-function InfoCardWithTrend({ title, changePercent, value, trendColorOverride }: InfoCardWithTrendProps) {
- const trendColor = changePercent > 0 ? "text-green-400" : "text-red-400"
- const trendIcon = changePercent > 0 ? <TrendingUp className="h-3.5 w-3.5 mr-1" /> : <TrendingUp className="h-3.5 w-3.5 mr-1 rotate-180" />
- const trendText = changePercent > 0 ? `+${changePercent}% from previous period` : `${changePercent}% from previous period`
- const trendClass = trendColorOverride || trendColor
-
- return (
- <Card className="border-gray-800 bg-gray-950">
- <CardHeader className="pb-2">
- <CardTitle className="text-sm font-medium">{title}</CardTitle>
- </CardHeader>
- <CardContent>
- <div className="text-2xl font-bold">{value}</div>
- <div className={`flex items-center text-xs ${trendClass}`}>
- {trendIcon}
- <span>{trendText}</span>
- </div>
- </CardContent>
- </Card>
- )
-}
-
-// Mock data for heatmaps
-const generateHeatmapData = (intensity = 1, seed = 42) => {
- const today = new Date()
- const data = []
-
- // Generate data for the past year (52 weeks)
- for (let i = 0; i < 364; i++) {
- const date = new Date(today)
- date.setDate(today.getDate() - i)
-
- // Use a simple algorithm to generate semi-random but consistent data
- const dayOfYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000)
- const value = Math.floor(Math.sin(dayOfYear * 0.1 + seed) * Math.cos(date.getMonth() * 0.3 + seed) * 10 * intensity)
-
- data.push({
- date: date.toISOString().split("T")[0],
- count: Math.max(0, value),
- })
- }
-
- // Sort by date (oldest first)
- return data.reverse()
-}
-
-// Generate mock data
-const reportsHeatmapData = generateHeatmapData(1.5, 42)
-const bansHeatmapData = generateHeatmapData(1, 123)
-
-// Mock data for charts
-const roomActivityData = [
- { name: "#general:matrix.org", reports: 42, bans: 15 },
- { name: "#support:matrix.org", reports: 28, bans: 9 },
- { name: "#community:matrix.org", reports: 17, bans: 5 },
- { name: "#development:matrix.org", reports: 8, bans: 2 },
- { name: "#random:matrix.org", reports: 5, bans: 1 },
-]
-
-const reportTypesData = [
- { id: 1, label: "Spam", value: 45, color: "#f97316" },
- { id: 2, label: "Harassment", value: 32, color: "#ef4444" },
- { id: 3, label: "Inappropriate Content", value: 28, color: "#ec4899" },
- { id: 4, label: "Scam", value: 15, color: "#f59e0b" },
- { id: 5, label: "Other", value: 10, color: "#6366f1" },
-]
-
-const bannedServersData = [
- { label: "spam.org", value: 28, secondaryLabel: "28 users" },
- { label: "scam.net", value: 17, secondaryLabel: "17 users" },
- { label: "badactor.io", value: 12, secondaryLabel: "12 users" },
- { label: "malicious.com", value: 9, secondaryLabel: "9 users" },
- { label: "troll.xyz", value: 7, secondaryLabel: "7 users" },
-]
-
-// Enhanced monthly activity data with more realistic patterns
-const monthlyActivityData = [
- { month: "Jan", reports: 24, bans: 8, year: 2025 },
- { month: "Feb", reports: 32, bans: 12, year: 2025 },
- { month: "Mar", reports: 45, bans: 18, year: 2025 },
- { month: "Apr", reports: 38, bans: 15, year: 2025 },
- { month: "May", reports: 29, bans: 11, year: 2025 },
- { month: "Jun", reports: 35, bans: 14, year: 2025 },
- { month: "Jul", reports: 42, bans: 17, year: 2025 },
- { month: "Aug", reports: 50, bans: 22, year: 2025 },
- { month: "Sep", reports: 43, bans: 19, year: 2025 },
- { month: "Oct", reports: 37, bans: 16, year: 2025 },
- { month: "Nov", reports: 31, bans: 13, year: 2025 },
- { month: "Dec", reports: 28, bans: 10, year: 2025 },
-]
-
-// Transform monthly data for the BarChart component
-const monthlyBarChartData = monthlyActivityData.map((item) => ({
- label: `${item.month} ${item.year}`,
- values: [
- { value: item.reports, color: "#9333ea", label: "Reports" },
- { value: item.bans, color: "#dc2626", label: "Bans" },
- ],
-}))
-
-interface AnalyticsDashboardProps {
- selectedTeam: {
- id: string
- name: string
- }
-}
-
-export function AnalyticsDashboard({ selectedTeam }: AnalyticsDashboardProps) {
- const [timeRange, setTimeRange] = useState("year")
- const [heatmapType, setHeatmapType] = useState("reports")
-
-
- const reportChangePercent = 12
-
- return (
- <div className="space-y-4">
- <div className="flex items-center justify-between">
- <h2 className="text-2xl font-bold">{selectedTeam.name} Analytics</h2>
- <div className="flex items-center gap-2">
- <Select value={timeRange} onValueChange={setTimeRange}>
- <SelectTrigger className="w-[120px] h-8 bg-gray-900 border-gray-800">
- <SelectValue placeholder="Time Range" />
- </SelectTrigger>
- <SelectContent className="bg-gray-900 border-gray-800">
- <SelectItem value="month">Past Month</SelectItem>
- <SelectItem value="quarter">Past Quarter</SelectItem>
- <SelectItem value="year">Past Year</SelectItem>
- <SelectItem value="all">All Time</SelectItem>
- </SelectContent>
- </Select>
-
- </div>
- </div>
-
- <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
- <InfoCardWithTrend title="Total Reports" changePercent={12} value={247} trendColorOverride={reportChangePercent > 0 ? "text-red-400" : "text-green-400"} />
- <InfoCardWithTrend title="Bans Issued" changePercent={5} value={98} />
- <InfoCardWithTrend title="Unique Reporters" changePercent={8} value={42} />
- <InfoCardWithTrend title="Avg. Response Time" changePercent={-15} value="1.4h" />
- </div>
-
- <Card className="border-gray-800 bg-gray-950">
- <CardHeader>
- <div className="flex items-center justify-between">
- <div>
- <CardTitle>Activity Heatmap</CardTitle>
- <CardDescription className="text-gray-400">
- Visualization of moderation activity over time
- </CardDescription>
- </div>
- <Tabs value={heatmapType} onValueChange={setHeatmapType}>
- <TabsList className="bg-gray-900">
- <TabsTrigger value="reports" className="text-xs">
- Reports
- </TabsTrigger>
- <TabsTrigger value="bans" className="text-xs">
- Bans
- </TabsTrigger>
- </TabsList>
- </Tabs>
- </div>
- </CardHeader>
- <CardContent className="overflow-hidden">
- <Heatmap
- data={heatmapType === "reports" ? reportsHeatmapData : bansHeatmapData}
- title={`${heatmapType === "reports" ? "Reports Activity" : "Ban Actions"} (Past Year)`}
- colorIntensityLabel={heatmapType === "reports" ? "reports" : "bans"}
- />
- </CardContent>
- </Card>
-
- <div className="grid gap-4 md:grid-cols-2">
- <Card className="border-gray-800 bg-gray-950">
- <CardHeader>
- <CardTitle>Room Activity</CardTitle>
- <CardDescription className="text-gray-400">Reports and bans by room</CardDescription>
- </CardHeader>
- <CardContent>
- <div className="space-y-4">
- {roomActivityData.map((room, index) => (
- <div key={index} className="space-y-2">
- <div className="flex items-center justify-between">
- <span className="text-sm font-medium">{room.name}</span>
- <span className="text-xs text-gray-400">{room.reports + room.bans} actions</span>
- </div>
- <div className="flex h-2 overflow-hidden rounded-full bg-gray-900">
- <div
- className="bg-purple-600"
- style={{ width: `${(room.reports / (room.reports + room.bans)) * 100}%` }}
- title={`${room.reports} reports`}
- ></div>
- <div
- className="bg-red-600"
- style={{ width: `${(room.bans / (room.reports + room.bans)) * 100}%` }}
- title={`${room.bans} bans`}
- ></div>
- </div>
- <div className="flex items-center justify-between text-xs text-gray-400">
- <div className="flex items-center gap-1">
- <div className="h-2 w-2 rounded-full bg-purple-600"></div>
- <span>{room.reports} reports</span>
- </div>
- <div className="flex items-center gap-1">
- <div className="h-2 w-2 rounded-full bg-red-600"></div>
- <span>{room.bans} bans</span>
- </div>
- </div>
- </div>
- ))}
- </div>
- </CardContent>
- </Card>
-
- <Card className="border-gray-800 bg-gray-950">
- <CardHeader>
- <CardTitle>Report Types</CardTitle>
- <CardDescription className="text-gray-400">Distribution of report reasons</CardDescription>
- </CardHeader>
- <CardContent>
- <PieChart data={reportTypesData} centerLabel="Report Types" />
- </CardContent>
- </Card>
- </div>
-
- <div className="grid gap-4 md:grid-cols-2">
- <Card className="border-gray-800 bg-gray-950">
- <CardHeader>
- <CardTitle>Monthly Activity</CardTitle>
- <CardDescription className="text-gray-400">Reports and bans over the past year</CardDescription>
- </CardHeader>
- <CardContent>
- <div className="h-[250px] w-full">
- <BarChart data={monthlyBarChartData} height={200} />
- </div>
- </CardContent>
- </Card>
-
- <Card className="border-gray-800 bg-gray-950">
- <CardHeader>
- <CardTitle>Most Banned Servers</CardTitle>
- <CardDescription className="text-gray-400">Servers with the most banned users</CardDescription>
- </CardHeader>
- <CardContent>
- <HorizontalBarChart data={bannedServersData} color="#dc2626" />
- </CardContent>
- </Card>
- </div>
-
- <Card className="border-gray-800 bg-gray-950">
- <CardHeader>
- <CardTitle>Moderation Efficiency</CardTitle>
- <CardDescription className="text-gray-400">
- Key performance indicators for your moderation team
- </CardDescription>
- </CardHeader>
- <CardContent>
- <div className="grid gap-4 md:grid-cols-3">
- <div className="space-y-2">
- <h3 className="text-sm font-medium text-gray-400">Average Response Time</h3>
- <div className="flex items-end gap-3">
- <span className="text-2xl font-bold">1.4h</span>
- <span className="text-xs text-green-400">-15% from last month</span>
- </div>
- <p className="text-xs text-gray-400">Time between report creation and moderator action</p>
- </div>
-
- <div className="space-y-2">
- <h3 className="text-sm font-medium text-gray-400">Resolution Rate</h3>
- <div className="flex items-end gap-3">
- <span className="text-2xl font-bold">92%</span>
- <span className="text-xs text-green-400">+3% from last month</span>
- </div>
- <p className="text-xs text-gray-400">Percentage of reports that were resolved</p>
- </div>
-
- <div className="space-y-2">
- <h3 className="text-sm font-medium text-gray-400">Moderator Activity</h3>
- <div className="flex items-end gap-3">
- <span className="text-2xl font-bold">4.2</span>
- <span className="text-xs text-gray-400">actions per day</span>
- </div>
- <p className="text-xs text-gray-400">Average number of moderation actions per day</p>
- </div>
- </div>
-
- <div className="mt-6">
- <h3 className="mb-2 text-sm font-medium text-gray-400">Moderator Performance</h3>
- <div className="space-y-3">
- <div className="flex items-center gap-3">
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-purple-900 text-xs font-medium">
- AM
- </div>
- <div className="flex-1">
- <div className="flex items-center justify-between">
- <span className="text-sm">@admin:matrix.org</span>
- <span className="text-xs text-gray-400">42 actions</span>
- </div>
- <div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-900">
- <div className="h-full rounded-full bg-purple-600" style={{ width: "85%" }}></div>
- </div>
- </div>
- </div>
-
- <div className="flex items-center gap-3">
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-900 text-xs font-medium">
- M1
- </div>
- <div className="flex-1">
- <div className="flex items-center justify-between">
- <span className="text-sm">@mod1:matrix.org</span>
- <span className="text-xs text-gray-400">36 actions</span>
- </div>
- <div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-900">
- <div className="h-full rounded-full bg-blue-600" style={{ width: "72%" }}></div>
- </div>
- </div>
- </div>
-
- <div className="flex items-center gap-3">
- <div className="flex h-8 w-8 items-center justify-center rounded-full bg-green-900 text-xs font-medium">
- M2
- </div>
- <div className="flex-1">
- <div className="flex items-center justify-between">
- <span className="text-sm">@mod2:matrix.org</span>
- <span className="text-xs text-gray-400">28 actions</span>
- </div>
- <div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-900">
- <div className="h-full rounded-full bg-green-600" style={{ width: "56%" }}></div>
- </div>
- </div>
- </div>
- </div>
- </div>
- </CardContent>
- </Card>
- </div>
- )
-}
diff --git a/src/app/dashboard/components/analytics-dashboard.tsx b/src/app/dashboard/components/analytics-dashboard.tsx
@@ -0,0 +1,356 @@
+import { useState } from "react"
+import { TrendingUp } from "lucide-react"
+
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
+
+import { PieChart } from "@/components/analytics/pie-chart"
+import { Heatmap } from "@/components/analytics/heatmap"
+import { BarChart, BarChartItem } from "@/components/analytics/bar-chart"
+import { HorizontalBarChart } from "@/components/analytics/horizontal-bar-chart"
+
+interface InfoCardWithTrendProps {
+ title: string;
+ changePercent: number;
+ value: string | number;
+ trendColorOverride?: string;
+}
+
+function InfoCardWithTrend({ title, changePercent, value, trendColorOverride }: InfoCardWithTrendProps) {
+ const trendColor = changePercent > 0 ? "text-green-400" : "text-red-400"
+ const trendIcon = changePercent > 0 ? <TrendingUp className="h-3.5 w-3.5 mr-1" /> : <TrendingUp className="h-3.5 w-3.5 mr-1 rotate-180" />
+ const trendText = changePercent > 0 ? `+${changePercent}% from previous period` : `${changePercent}% from previous period`
+ const trendClass = trendColorOverride || trendColor
+
+ return (
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader className="pb-2">
+ <CardTitle className="text-sm font-medium">{title}</CardTitle>
+ </CardHeader>
+ <CardContent>
+ <div className="text-2xl font-bold">{value}</div>
+ <div className={`flex items-center text-xs ${trendClass}`}>
+ {trendIcon}
+ <span>{trendText}</span>
+ </div>
+ </CardContent>
+ </Card>
+ )
+}
+
+// Mock data for heatmaps
+const generateHeatmapData = (intensity = 1, seed = 42) => {
+ const today = new Date()
+ const data = []
+
+ // Generate data for the past year (52 weeks)
+ for (let i = 0; i < 364; i++) {
+ const date = new Date(today)
+ date.setDate(today.getDate() - i)
+
+ // Use a simple algorithm to generate semi-random but consistent data
+ const dayOfYear = Math.floor((date.getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000)
+ const value = Math.floor(Math.sin(dayOfYear * 0.1 + seed) * Math.cos(date.getMonth() * 0.3 + seed) * 10 * intensity)
+
+ data.push({
+ date: date.toISOString().split("T")[0],
+ count: Math.max(0, value),
+ })
+ }
+
+ // Sort by date (oldest first)
+ return data.reverse()
+}
+
+// Generate mock data
+const reportsHeatmapData = generateHeatmapData(1.5, 42)
+const bansHeatmapData = generateHeatmapData(1, 123)
+
+// Mock data for charts
+const roomActivityData = [
+ { name: "#general:matrix.org", reports: 42, bans: 15 },
+ { name: "#support:matrix.org", reports: 28, bans: 9 },
+ { name: "#community:matrix.org", reports: 17, bans: 5 },
+ { name: "#development:matrix.org", reports: 8, bans: 2 },
+ { name: "#random:matrix.org", reports: 5, bans: 1 },
+]
+
+const reportTypesData = [
+ { id: 1, label: "Spam", value: 45, color: "#f97316" },
+ { id: 2, label: "Harassment", value: 32, color: "#ef4444" },
+ { id: 3, label: "Inappropriate Content", value: 28, color: "#ec4899" },
+ { id: 4, label: "Scam", value: 15, color: "#f59e0b" },
+ { id: 5, label: "Other", value: 10, color: "#6366f1" },
+]
+
+const bannedServersData = [
+ { label: "spam.org", value: 28, secondaryLabel: "28 users" },
+ { label: "scam.net", value: 17, secondaryLabel: "17 users" },
+ { label: "badactor.io", value: 12, secondaryLabel: "12 users" },
+ { label: "malicious.com", value: 9, secondaryLabel: "9 users" },
+ { label: "troll.xyz", value: 7, secondaryLabel: "7 users" },
+]
+
+// Enhanced monthly activity data with more realistic patterns
+const monthlyActivityData = [
+ { month: "Jan", reports: 24, bans: 8, year: 2025 },
+ { month: "Feb", reports: 32, bans: 12, year: 2025 },
+ { month: "Mar", reports: 45, bans: 18, year: 2025 },
+ { month: "Apr", reports: 38, bans: 15, year: 2025 },
+ { month: "May", reports: 29, bans: 11, year: 2025 },
+ { month: "Jun", reports: 35, bans: 14, year: 2025 },
+ { month: "Jul", reports: 42, bans: 17, year: 2025 },
+ { month: "Aug", reports: 50, bans: 22, year: 2025 },
+ { month: "Sep", reports: 43, bans: 19, year: 2025 },
+ { month: "Oct", reports: 37, bans: 16, year: 2025 },
+ { month: "Nov", reports: 31, bans: 13, year: 2025 },
+ { month: "Dec", reports: 28, bans: 10, year: 2025 },
+]
+
+// Transform monthly data for the BarChart component
+const monthlyBarChartData: BarChartItem[] = monthlyActivityData.map((item) => ({
+ label: `${item.month}`,
+ values: [
+ { value: item.reports, color: "#9333ea", label: "Reports" },
+ { value: item.bans, color: "#dc2626", label: "Bans" },
+ ],
+}))
+
+interface AnalyticsDashboardProps {
+ selectedTeam: {
+ id: string
+ name: string
+ }
+}
+
+export function AnalyticsDashboard({ selectedTeam }: AnalyticsDashboardProps) {
+ const [timeRange, setTimeRange] = useState("year")
+ const [heatmapType, setHeatmapType] = useState("reports")
+
+
+ const reportChangePercent = 12
+
+ return (
+ <div className="space-y-4">
+ <div className="flex items-center justify-between">
+ <h2 className="text-2xl font-bold">{selectedTeam.name} Analytics</h2>
+ <div className="flex items-center gap-2">
+ <Select value={timeRange} onValueChange={setTimeRange}>
+ <SelectTrigger className="w-[120px] h-8 bg-gray-900 border-gray-800">
+ <SelectValue placeholder="Time Range" />
+ </SelectTrigger>
+ <SelectContent className="bg-gray-900 border-gray-800">
+ <SelectItem value="month">Past Month</SelectItem>
+ <SelectItem value="quarter">Past Quarter</SelectItem>
+ <SelectItem value="year">Past Year</SelectItem>
+ <SelectItem value="all">All Time</SelectItem>
+ </SelectContent>
+ </Select>
+
+ </div>
+ </div>
+
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
+ <InfoCardWithTrend title="Total Reports" changePercent={12} value={247} trendColorOverride={reportChangePercent > 0 ? "text-red-400" : "text-green-400"} />
+ <InfoCardWithTrend title="Bans Issued" changePercent={5} value={98} />
+ <InfoCardWithTrend title="Unique Reporters" changePercent={8} value={42} />
+ <InfoCardWithTrend title="Avg. Response Time" changePercent={-15} value="1.4h" />
+ </div>
+
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader>
+ <div className="flex items-center justify-between">
+ <div>
+ <CardTitle>Activity Heatmap</CardTitle>
+ <CardDescription className="text-gray-400">
+ Visualization of moderation activity over time
+ </CardDescription>
+ </div>
+ <Tabs value={heatmapType} onValueChange={setHeatmapType}>
+ <TabsList className="bg-gray-900">
+ <TabsTrigger value="reports" className="text-xs">
+ Reports
+ </TabsTrigger>
+ <TabsTrigger value="bans" className="text-xs">
+ Bans
+ </TabsTrigger>
+ </TabsList>
+ </Tabs>
+ </div>
+ </CardHeader>
+ <CardContent className="overflow-hidden">
+ <Heatmap
+ data={heatmapType === "reports" ? reportsHeatmapData : bansHeatmapData}
+ title={`${heatmapType === "reports" ? "Reports Activity" : "Ban Actions"} (Past Year)`}
+ colorIntensityLabel={heatmapType === "reports" ? "reports" : "bans"}
+ />
+ </CardContent>
+ </Card>
+
+ <div className="grid gap-4 md:grid-cols-2">
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader>
+ <CardTitle>Room Activity</CardTitle>
+ <CardDescription className="text-gray-400">Reports and bans by room</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <div className="space-y-4">
+ {roomActivityData.map((room, index) => (
+ <div key={index} className="space-y-2">
+ <div className="flex items-center justify-between">
+ <span className="text-sm font-medium">{room.name}</span>
+ <span className="text-xs text-gray-400">{room.reports + room.bans} actions</span>
+ </div>
+ <div className="flex h-2 overflow-hidden rounded-full bg-gray-900">
+ <div
+ className="bg-purple-600"
+ style={{ width: `${(room.reports / (room.reports + room.bans)) * 100}%` }}
+ title={`${room.reports} reports`}
+ ></div>
+ <div
+ className="bg-red-600"
+ style={{ width: `${(room.bans / (room.reports + room.bans)) * 100}%` }}
+ title={`${room.bans} bans`}
+ ></div>
+ </div>
+ <div className="flex items-center justify-between text-xs text-gray-400">
+ <div className="flex items-center gap-1">
+ <div className="h-2 w-2 rounded-full bg-purple-600"></div>
+ <span>{room.reports} reports</span>
+ </div>
+ <div className="flex items-center gap-1">
+ <div className="h-2 w-2 rounded-full bg-red-600"></div>
+ <span>{room.bans} bans</span>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ </CardContent>
+ </Card>
+
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader>
+ <CardTitle>Report Types</CardTitle>
+ <CardDescription className="text-gray-400">Distribution of report reasons</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <PieChart data={reportTypesData} centerLabel="Report Types" />
+ </CardContent>
+ </Card>
+ </div>
+
+ <div className="grid gap-4 md:grid-cols-2">
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader>
+ <CardTitle>Monthly Activity</CardTitle>
+ <CardDescription className="text-gray-400">Reports and bans over the past year</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <BarChart data={monthlyBarChartData} height={200} />
+ </CardContent>
+ </Card>
+
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader>
+ <CardTitle>Most Banned Servers</CardTitle>
+ <CardDescription className="text-gray-400">Servers with the most banned users</CardDescription>
+ </CardHeader>
+ <CardContent>
+ <HorizontalBarChart data={bannedServersData} color="#dc2626" />
+ </CardContent>
+ </Card>
+ </div>
+
+ <Card className="border-gray-800 bg-gray-950">
+ <CardHeader>
+ <CardTitle>Moderation Efficiency</CardTitle>
+ <CardDescription className="text-gray-400">
+ Key performance indicators for your moderation team
+ </CardDescription>
+ </CardHeader>
+ <CardContent>
+ <div className="grid gap-4 md:grid-cols-3">
+ <div className="space-y-2">
+ <h3 className="text-sm font-medium text-gray-400">Average Response Time</h3>
+ <div className="flex items-end gap-3">
+ <span className="text-2xl font-bold">1.4h</span>
+ <span className="text-xs text-green-400">-15% from last month</span>
+ </div>
+ <p className="text-xs text-gray-400">Time between report creation and moderator action</p>
+ </div>
+
+ <div className="space-y-2">
+ <h3 className="text-sm font-medium text-gray-400">Resolution Rate</h3>
+ <div className="flex items-end gap-3">
+ <span className="text-2xl font-bold">92%</span>
+ <span className="text-xs text-green-400">+3% from last month</span>
+ </div>
+ <p className="text-xs text-gray-400">Percentage of reports that were resolved</p>
+ </div>
+
+ <div className="space-y-2">
+ <h3 className="text-sm font-medium text-gray-400">Moderator Activity</h3>
+ <div className="flex items-end gap-3">
+ <span className="text-2xl font-bold">4.2</span>
+ <span className="text-xs text-gray-400">actions per day</span>
+ </div>
+ <p className="text-xs text-gray-400">Average number of moderation actions per day</p>
+ </div>
+ </div>
+
+ <div className="mt-6">
+ <h3 className="mb-2 text-sm font-medium text-gray-400">Moderator Performance</h3>
+ <div className="space-y-3">
+ <div className="flex items-center gap-3">
+ <div className="flex h-8 w-8 items-center justify-center rounded-full bg-purple-900 text-xs font-medium">
+ AM
+ </div>
+ <div className="flex-1">
+ <div className="flex items-center justify-between">
+ <span className="text-sm">@admin:matrix.org</span>
+ <span className="text-xs text-gray-400">42 actions</span>
+ </div>
+ <div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-900">
+ <div className="h-full rounded-full bg-purple-600" style={{ width: "85%" }}></div>
+ </div>
+ </div>
+ </div>
+
+ <div className="flex items-center gap-3">
+ <div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-900 text-xs font-medium">
+ M1
+ </div>
+ <div className="flex-1">
+ <div className="flex items-center justify-between">
+ <span className="text-sm">@mod1:matrix.org</span>
+ <span className="text-xs text-gray-400">36 actions</span>
+ </div>
+ <div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-900">
+ <div className="h-full rounded-full bg-blue-600" style={{ width: "72%" }}></div>
+ </div>
+ </div>
+ </div>
+
+ <div className="flex items-center gap-3">
+ <div className="flex h-8 w-8 items-center justify-center rounded-full bg-green-900 text-xs font-medium">
+ M2
+ </div>
+ <div className="flex-1">
+ <div className="flex items-center justify-between">
+ <span className="text-sm">@mod2:matrix.org</span>
+ <span className="text-xs text-gray-400">28 actions</span>
+ </div>
+ <div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-900">
+ <div className="h-full rounded-full bg-green-600" style={{ width: "56%" }}></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </CardContent>
+ </Card>
+ </div>
+ )
+}
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
@@ -5,7 +5,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { mockTeams, Team, TeamManagement } from "./team-management"
import Bans from "./components/bans-pane"
-import { AnalyticsDashboard } from "./analytics-dashboard"
+import { AnalyticsDashboard } from "./components/analytics-dashboard"
import { ReportsTab } from "./components/reports-tab"
import { DashboardHeader } from "./components/dashboard-header"
import { OverviewTab } from "./components/overview-tab"
diff --git a/src/components/analytics/bar-chart.tsx b/src/components/analytics/bar-chart.tsx
@@ -1,6 +1,6 @@
-"use client"
+import { useRef, useState } from "react"
-interface BarChartItem {
+export interface BarChartItem {
label: string
values: {
value: number
@@ -16,47 +16,93 @@ interface BarChartProps {
}
export function BarChart({ data, height = 200, className = "" }: BarChartProps) {
+ const [hoveredGroup, setHoveredGroup] = useState<number | null>(null)
+ const chartRef = useRef<HTMLDivElement>(null)
+
// Find the maximum value across all data points for scaling
const maxValue = Math.max(...data.flatMap((item) => item.values.map((v) => v.value)))
+ // Calculate bar width based on number of data points
+ const barWidth = 100 / (data.length * 2) // Half the space for gaps
+
return (
<div className={`w-full ${className}`}>
- <div className={`h-[${height}px] w-full`}>
- <div className="flex h-full items-end gap-2">
- {data.map((item, index) => (
- <div key={index} className="group relative flex w-full flex-col items-center">
- <div className="relative flex h-full w-full flex-col justify-end">
- {item.values.map((value, valueIndex) => {
- const barHeight = (value.value / maxValue) * 100
- return (
- <div
- key={valueIndex}
- className={`w-full rounded-t transition-all group-hover:opacity-90 ${valueIndex > 0 ? "mt-0.5" : ""}`}
- style={{
- height: `${barHeight}%`,
- backgroundColor: value.color,
- }}
- ></div>
- )
- })}
- </div>
- <div className="absolute bottom-0 left-0 right-0 h-0 overflow-hidden bg-gray-800 opacity-0 transition-all group-hover:h-auto group-hover:opacity-100 z-10 rounded-md">
- <div className="p-2 text-center text-xs">
- <div className="font-medium">{item.label}</div>
- {item.values.map((value, valueIndex) => (
- <div key={valueIndex} style={{ color: value.color }}>
- {value.label}: {value.value}
- </div>
- ))}
+ <div
+ ref={chartRef}
+ className="relative w-full"
+ style={{ height: `${height}px` }}
+ >
+ {/* Y-axis grid lines (optional) */}
+ <div className="absolute inset-0 flex flex-col justify-between pointer-events-none">
+ {[0, 1, 2, 3, 4].map((i) => (
+ <div
+ key={i}
+ className="w-full border-t border-gray-800"
+ style={{ height: '20%' }}
+ ></div>
+ ))}
+ </div>
+
+ {/* Bars container */}
+ <div className="absolute inset-0 flex items-end">
+ {data.map((item, groupIndex) => (
+ <div
+ key={groupIndex}
+ className="h-full flex-1 flex items-end justify-center group"
+ onMouseEnter={() => setHoveredGroup(groupIndex)}
+ onMouseLeave={() => setHoveredGroup(null)}
+ >
+ <div className="relative h-full w-full flex items-end justify-center">
+ {/* Bar group */}
+ <div className="flex gap-1 items-end h-full">
+ {item.values.map((value, valueIndex) => {
+ const barHeight = (value.value / maxValue) * 100
+ return (
+ <div
+ key={valueIndex}
+ className="relative transition-all duration-200"
+ style={{
+ height: `${barHeight}%`,
+ width: `${barWidth}%`,
+ minWidth: '12px',
+ maxWidth: '40px',
+ backgroundColor: value.color,
+ opacity: hoveredGroup === groupIndex ? 0.8 : 1
+ }}
+ >
+ {/* Optional value label on top of bar */}
+ {hoveredGroup === groupIndex && (
+ <div className="absolute -top-6 left-1/2 transform -translate-x-1/2 text-xs whitespace-nowrap">
+ {value.value}
+ </div>
+ )}
+ </div>
+ )
+ })}
</div>
+
+ {/* Tooltip */}
+ {hoveredGroup === groupIndex && (
+ <div className="absolute bottom-full mb-2 bg-gray-800 text-white text-xs rounded p-2 z-10">
+ <div className="font-medium">{item.label}</div>
+ {item.values.map((value, i) => (
+ <div key={i} className="flex justify-between gap-2">
+ <span style={{ color: value.color }}>{value.label}:</span>
+ <span>{value.value}</span>
+ </div>
+ ))}
+ </div>
+ )}
</div>
</div>
))}
</div>
</div>
- <div className="mt-2 flex w-full justify-between">
+
+ {/* X-axis labels */}
+ <div className="mt-2 grid w-full" style={{ gridTemplateColumns: `repeat(${data.length}, 1fr)` }}>
{data.map((item, index) => (
- <div key={index} className="text-center text-xs text-gray-400">
+ <div key={index} className="text-center text-xs text-gray-400 truncate px-1">
{item.label}
</div>
))}