page.tsx (19430B)
1 import { ChevronDown, Users } from "lucide-react" 2 import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" 3 import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" 4 import { Heatmap } from "@/components/analytics/heatmap" 5 import { BannedServerData, generateHeatmapData, getBannedServersConfig, getMonthlyActivityConfig, getReportTypesConfig, getRoomActivityConfig, MonthlyActivityData, ReportTypeData, RoomActivityData } from "@/components/analytics/chart-configs"; 6 import { PlotlyChart } from "@/components/analytics/plotly-chart"; 7 import { Button } from "@/components/ui/button"; 8 import Link from "next/link"; 9 import InfoCardWithTrend from "@/components/analytics/info-card-with-trend"; 10 import LayoutWrapper from "@/components/dashboard/layoutWrapper"; 11 import { cookies } from "next/headers" 12 import { User } from "@/lib/auth" 13 import { listBots } from "@/lib/api" 14 import TabNavigation from "@/components/dashboard/tab-navigation"; 15 import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; 16 17 // Mock data for charts 18 const roomActivityData: RoomActivityData[] = [ 19 { name: "#general:matrix.org", reports: 42, bans: 15 }, 20 { name: "#support:matrix.org", reports: 28, bans: 9 }, 21 { name: "#community:matrix.org", reports: 17, bans: 5 }, 22 { name: "#development:matrix.org", reports: 8, bans: 2 }, 23 { name: "#random:matrix.org", reports: 5, bans: 1 }, 24 ] 25 26 const reportTypesData: ReportTypeData[] = [ 27 { id: 1, label: "Spam", value: 45, color: "#f97316" }, 28 { id: 2, label: "Harassment", value: 32, color: "#ef4444" }, 29 { id: 3, label: "Inappropriate Content", value: 28, color: "#ec4899" }, 30 { id: 4, label: "Scam", value: 15, color: "#f59e0b" }, 31 { id: 5, label: "Other", value: 10, color: "#6366f1" }, 32 ] 33 34 const bannedServersData: BannedServerData[] = [ 35 { label: "spam.org", value: 28, secondaryLabel: "28 users" }, 36 { label: "scam.net", value: 17, secondaryLabel: "17 users" }, 37 { label: "badactor.io", value: 12, secondaryLabel: "12 users" }, 38 { label: "malicious.com", value: 9, secondaryLabel: "9 users" }, 39 { label: "troll.xyz", value: 7, secondaryLabel: "7 users" }, 40 ] 41 42 // Enhanced monthly activity data with more realistic patterns 43 const monthlyActivityData: MonthlyActivityData[] = [ 44 { month: "Jan", reports: 24, bans: 8, year: 2025 }, 45 { month: "Feb", reports: 32, bans: 12, year: 2025 }, 46 { month: "Mar", reports: 45, bans: 18, year: 2025 }, 47 { month: "Apr", reports: 38, bans: 15, year: 2025 }, 48 { month: "May", reports: 29, bans: 11, year: 2025 }, 49 { month: "Jun", reports: 35, bans: 14, year: 2025 }, 50 { month: "Jul", reports: 42, bans: 17, year: 2025 }, 51 { month: "Aug", reports: 50, bans: 22, year: 2025 }, 52 { month: "Sep", reports: 43, bans: 19, year: 2025 }, 53 { month: "Oct", reports: 37, bans: 16, year: 2025 }, 54 { month: "Nov", reports: 31, bans: 13, year: 2025 }, 55 { month: "Dec", reports: 28, bans: 10, year: 2025 }, 56 ] 57 58 59 export default async function AnalyticsDashboard({ 60 searchParams, 61 }: { 62 searchParams: Promise<{ [key: string]: string | string[] | undefined }> 63 }) { 64 const params = await searchParams; 65 const teamIdParam = params.team as string | undefined; 66 const timeRange = (params.timeRange as string | undefined) ?? "year" 67 const heatmapType = (params.heatmapType as string | undefined) ?? "reports" 68 const cookieStore = await cookies() 69 const session: User = JSON.parse(cookieStore.get("d4all_session")?.value ?? "{}") 70 if (!session.token) { 71 return <div className="flex h-screen w-full items-center justify-center">Loading...</div> 72 } 73 const listData = await listBots(session.matrixId, session.token); 74 75 if (!listData) { 76 return <div className="flex h-screen w-full items-center justify-center">Loading...</div> 77 } 78 79 const selectedBot = listData.bots.find((team) => team.id === teamIdParam) ?? listData.bots[0]; 80 if (!selectedBot) { 81 // TODO: Redirect to register page? 82 return <div className="flex h-screen w-full items-center justify-center">Loading...</div> 83 } 84 85 // Generate mock data 86 const reportsHeatmapData = generateHeatmapData(1.5, 42) 87 const bansHeatmapData = generateHeatmapData(1, 123) 88 89 // Get chart configurations 90 const roomActivityConfig = getRoomActivityConfig(roomActivityData) 91 const reportTypesConfig = getReportTypesConfig(reportTypesData) 92 const monthlyActivityConfig = getMonthlyActivityConfig(monthlyActivityData) 93 const bannedServersConfig = getBannedServersConfig(bannedServersData) 94 95 96 const reportChangePercent = 12 97 98 return ( 99 <LayoutWrapper listData={listData} activeTab="analytics" teamIdParam={teamIdParam}> 100 <TabNavigation selectedBot={selectedBot} currentTab="analytics" /> 101 <div className="space-y-4"> 102 <div className="flex items-center justify-end"> 103 <DropdownMenu> 104 <DropdownMenuTrigger asChild> 105 <Button variant="outline" className="h-8 w-[120px] bg-gray-900 border border-gray-800 text-gray-400 rounded-md flex items-center justify-between"> 106 <span>{timeRange.charAt(0).toUpperCase() + timeRange.slice(1)}</span> 107 <ChevronDown className="ml-2 h-4 w-4" /> 108 </Button> 109 </DropdownMenuTrigger> 110 <DropdownMenuContent className="flex flex-col gap-1 w-56 bg-gray-900 border-gray-800"> 111 <DropdownMenuItem asChild> 112 <Link 113 href={`/dashboard/analytics?team=${teamIdParam}&timeRange=month&heatmapType=${heatmapType}`} 114 className={timeRange === "month" ? "bg-gray-800 text-white" : "text-gray-400 hover:bg-gray-800"} 115 > 116 Past Month 117 </Link> 118 </DropdownMenuItem> 119 <DropdownMenuItem asChild> 120 <Link 121 href={`/dashboard/analytics?team=${teamIdParam}&timeRange=quarter&heatmapType=${heatmapType}`} 122 className={timeRange === "quarter" ? "bg-gray-800 text-white" : "text-gray-400 hover:bg-gray-800"} 123 > 124 Past Quarter 125 </Link> 126 </DropdownMenuItem> 127 <DropdownMenuItem asChild> 128 <Link 129 href={`/dashboard/analytics?team=${teamIdParam}&timeRange=year&heatmapType=${heatmapType}`} 130 className={timeRange === "year" ? "bg-gray-800 text-white" : "text-gray-400 hover:bg-gray-800"} 131 > 132 Past Year 133 </Link> 134 </DropdownMenuItem> 135 <DropdownMenuItem asChild> 136 <Link 137 href={`/dashboard/analytics?team=${teamIdParam}&timeRange=all&heatmapType=${heatmapType}`} 138 className={timeRange === "all" ? "bg-gray-800 text-white" : "text-gray-400 hover:bg-gray-800"} 139 > 140 All Time 141 </Link> 142 </DropdownMenuItem> 143 </DropdownMenuContent> 144 </DropdownMenu> 145 </div> 146 147 <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"> 148 <InfoCardWithTrend title="Total Reports" changePercent={12} value={247} trendColorOverride={reportChangePercent > 0 ? "text-red-400" : "text-green-400"} /> 149 <InfoCardWithTrend title="Bans Issued" changePercent={5} value={98} /> 150 <InfoCardWithTrend title="Unique Reporters" changePercent={8} value={42} /> 151 <InfoCardWithTrend title="Avg. Response Time" changePercent={-15} value="1.4h" /> 152 </div> 153 154 <Card className="border-gray-800 bg-gray-950"> 155 <CardHeader> 156 <div className="flex items-center justify-between"> 157 <div> 158 <CardTitle>Activity Heatmap</CardTitle> 159 <CardDescription className="text-gray-400"> 160 Visualization of moderation activity over time 161 </CardDescription> 162 </div> 163 <Tabs value={heatmapType}> 164 <TabsList className="bg-gray-900"> 165 <TabsTrigger value="reports" className="text-xs" asChild> 166 <Link 167 href={`/dashboard/analytics?team=${teamIdParam}&timeRange=${timeRange}&heatmapType=reports`}> 168 Reports 169 </Link> 170 </TabsTrigger> 171 <TabsTrigger value="bans" className="text-xs" asChild> 172 <Link 173 href={`/dashboard/analytics?team=${teamIdParam}&timeRange=${timeRange}&heatmapType=bans`}> 174 Bans 175 </Link> 176 </TabsTrigger> 177 </TabsList> 178 </Tabs> 179 </div> 180 </CardHeader> 181 <CardContent className="overflow-hidden"> 182 <Heatmap 183 data={heatmapType === "reports" ? reportsHeatmapData : bansHeatmapData} 184 title={`${heatmapType === "reports" ? "Reports Activity" : "Ban Actions"} (Past Year)`} 185 colorIntensityLabel={heatmapType === "reports" ? "reports" : "bans"} 186 /> 187 </CardContent> 188 </Card> 189 190 <div className="grid gap-4 md:grid-cols-2"> 191 <Card className="border-gray-800 bg-gray-950"> 192 <CardHeader> 193 <CardTitle>Room Activity</CardTitle> 194 <CardDescription className="text-gray-400">Reports and bans by room</CardDescription> 195 </CardHeader> 196 <CardContent> 197 <PlotlyChart data={roomActivityConfig.data} layout={roomActivityConfig.layout} /> 198 </CardContent> 199 </Card> 200 201 <Card className="border-gray-800 bg-gray-950"> 202 <CardHeader> 203 <CardTitle>Report Types</CardTitle> 204 <CardDescription className="text-gray-400">Distribution of report reasons</CardDescription> 205 </CardHeader> 206 <CardContent> 207 <PlotlyChart data={reportTypesConfig.data} layout={reportTypesConfig.layout} /> 208 </CardContent> 209 </Card> 210 </div> 211 212 <div className="grid gap-4 md:grid-cols-2"> 213 <Card className="border-gray-800 bg-gray-950"> 214 <CardHeader> 215 <CardTitle>Monthly Activity</CardTitle> 216 <CardDescription className="text-gray-400">Reports and bans over the past year</CardDescription> 217 </CardHeader> 218 <CardContent> 219 <PlotlyChart data={monthlyActivityConfig.data} layout={monthlyActivityConfig.layout} /> 220 </CardContent> 221 </Card> 222 223 <Card className="border-gray-800 bg-gray-950"> 224 <CardHeader> 225 <CardTitle>Most Banned Servers</CardTitle> 226 <CardDescription className="text-gray-400">Servers with the most banned users</CardDescription> 227 </CardHeader> 228 <CardContent> 229 <PlotlyChart data={bannedServersConfig.data} layout={bannedServersConfig.layout} /> 230 <div className="mt-4 flex items-center justify-center"> 231 <Button variant="outline" size="sm" className="border-gray-800 text-gray-400 hover:text-white" asChild> 232 <Link href={`/dashboard/bans?team=${selectedBot.id}&heatmapType=${heatmapType}&timeRange=${timeRange}`}> 233 <Users className="mr-2 h-4 w-4" /> 234 View All Banned Servers 235 </Link> 236 </Button> 237 </div> 238 </CardContent> 239 </Card> 240 </div> 241 242 <Card className="border-gray-800 bg-gray-950"> 243 <CardHeader> 244 <CardTitle>Moderation Efficiency</CardTitle> 245 <CardDescription className="text-gray-400"> 246 Key performance indicators for your moderation team 247 </CardDescription> 248 </CardHeader> 249 <CardContent> 250 <div className="grid gap-4 md:grid-cols-3"> 251 <div className="space-y-2"> 252 <h3 className="text-sm font-medium text-gray-400">Average Response Time</h3> 253 <div className="flex items-end gap-2"> 254 <span className="text-2xl font-bold">1.4h</span> 255 <span className="text-xs text-green-400">-15% from last month</span> 256 </div> 257 <p className="text-xs text-gray-400">Time between report creation and moderator action</p> 258 </div> 259 260 <div className="space-y-2"> 261 <h3 className="text-sm font-medium text-gray-400">Resolution Rate</h3> 262 <div className="flex items-end gap-2"> 263 <span className="text-2xl font-bold">92%</span> 264 <span className="text-xs text-green-400">+3% from last month</span> 265 </div> 266 <p className="text-xs text-gray-400">Percentage of reports that were resolved</p> 267 </div> 268 269 <div className="space-y-2"> 270 <h3 className="text-sm font-medium text-gray-400">Moderator Activity</h3> 271 <div className="flex items-end gap-2"> 272 <span className="text-2xl font-bold">4.2</span> 273 <span className="text-xs text-gray-400">actions per day</span> 274 </div> 275 <p className="text-xs text-gray-400">Average number of moderation actions per day</p> 276 </div> 277 </div> 278 279 <div className="mt-6"> 280 <h3 className="mb-2 text-sm font-medium text-gray-400">Moderator Performance</h3> 281 <div className="space-y-3"> 282 <div className="flex items-center gap-3"> 283 <div className="flex h-8 w-8 items-center justify-center rounded-full bg-purple-900 text-xs font-medium"> 284 AM 285 </div> 286 <div className="flex-1"> 287 <div className="flex items-center justify-between"> 288 <span className="text-sm">@admin:matrix.org</span> 289 <span className="text-xs text-gray-400">42 actions</span> 290 </div> 291 <div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-900"> 292 <div className="h-full rounded-full bg-purple-600" style={{ width: "85%" }}></div> 293 </div> 294 </div> 295 </div> 296 297 <div className="flex items-center gap-3"> 298 <div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-900 text-xs font-medium"> 299 M1 300 </div> 301 <div className="flex-1"> 302 <div className="flex items-center justify-between"> 303 <span className="text-sm">@mod1:matrix.org</span> 304 <span className="text-xs text-gray-400">36 actions</span> 305 </div> 306 <div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-900"> 307 <div className="h-full rounded-full bg-blue-600" style={{ width: "72%" }}></div> 308 </div> 309 </div> 310 </div> 311 312 <div className="flex items-center gap-3"> 313 <div className="flex h-8 w-8 items-center justify-center rounded-full bg-green-900 text-xs font-medium"> 314 M2 315 </div> 316 <div className="flex-1"> 317 <div className="flex items-center justify-between"> 318 <span className="text-sm">@mod2:matrix.org</span> 319 <span className="text-xs text-gray-400">28 actions</span> 320 </div> 321 <div className="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-gray-900"> 322 <div className="h-full rounded-full bg-green-600" style={{ width: "56%" }}></div> 323 </div> 324 </div> 325 </div> 326 </div> 327 </div> 328 </CardContent> 329 </Card> 330 </div> 331 </LayoutWrapper> 332 ) 333 }