diff --git a/frontend/app/leaderboards/page.tsx b/frontend/app/leaderboards/page.tsx index 9f698d0..36c9dda 100644 --- a/frontend/app/leaderboards/page.tsx +++ b/frontend/app/leaderboards/page.tsx @@ -2,53 +2,61 @@ import { useEffect, useState } from "react" import { getApiUrl } from "@/lib/api-config" -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import Link from "next/link" -import { Star, MapPin, Music } from "lucide-react" +import { Star, MapPin, Music, User, Trophy, Calendar, Sparkles } from "lucide-react" +import { Badge } from "@/components/ui/badge" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" interface TopShow { - show: { - id: number - date: string - venue_id: number - } - venue: { - id: number - name: string - city: string - state: string - } + show: { id: number; date: string; venue_id: number } + venue: { id: number; name: string; city: string; state: string } avg_score: number review_count: number } interface TopVenue { - venue: { - id: number - name: string - city: string - state: string - } + venue: { id: number; name: string; city: string; state: string } avg_score: number review_count: number } +interface TopPerformance { + performance: { id: number; show_id: number; song_id: number; notes: string | null } + song: { id: number; title: string; original_artist: string | null } + show: { id: number; date: string } + venue: { id: number; name: string; city: string; state: string } + avg_score: number + rating_count: number +} + +interface TopUser { + profile: { id: number; user_id: number } + review_count: number +} export default function LeaderboardsPage() { const [topShows, setTopShows] = useState([]) const [topVenues, setTopVenues] = useState([]) + const [topPerformances, setTopPerformances] = useState([]) + const [topUsers, setTopUsers] = useState([]) const [loading, setLoading] = useState(true) useEffect(() => { const fetchData = async () => { try { - const [showsRes, venuesRes] = await Promise.all([ + const [showsRes, venuesRes, perfsRes, usersRes] = await Promise.all([ fetch(`${getApiUrl()}/leaderboards/shows/top`), - fetch(`${getApiUrl()}/leaderboards/venues/top`) + fetch(`${getApiUrl()}/leaderboards/venues/top`), + fetch(`${getApiUrl()}/leaderboards/performances/top`), + fetch(`${getApiUrl()}/leaderboards/users/active`) ]) setTopShows(await showsRes.json()) setTopVenues(await venuesRes.json()) + setTopPerformances(await perfsRes.json()) + setTopUsers(await usersRes.json()) } catch (error) { console.error("Failed to fetch leaderboards:", error) } finally { @@ -59,89 +67,175 @@ export default function LeaderboardsPage() { fetchData() }, []) - if (loading) return
Loading leaderboards...
+ const RankIcon = ({ rank }: { rank: number }) => { + if (rank === 1) return + if (rank === 2) return + if (rank === 3) return + return {rank} + } + + if (loading) { + return ( +
+
+
+

Computing Heady Stats...

+
+
+ ) + } return ( -
+
-

Leaderboards

-

- Top rated shows and venues. +

+ Leaderboards +

+

+ Discover the highest rated shows, legendary jams, and top contributors.

-
- {/* Top Shows */} - - - - - Top Rated Shows - - - -
- {topShows.map((item, i) => ( -
-
- - {i + 1} - - - {new Date(item.show.date).toLocaleDateString()} - - {item.venue?.name ? `${item.venue.name} (${item.venue.city}, ${item.venue.state})` : ""} - - + + + Heady Jams + Top Shows + Venues + Contributors + + + {/* HEADY JAMS CONTENT */} + + +
+ {topPerformances.map((item, i) => ( +
+
+
-
- - {item.avg_score} - ({item.review_count}) +
+
+ + {item.song.title} + + {item.performance.notes && ( + + {item.performance.notes} + + )} +
+
+ + + {new Date(item.show.date).toLocaleDateString(undefined, { + weekday: 'short', year: 'numeric', month: 'short', day: 'numeric' + })} + + + {item.venue.name} +
+
+
+
+ + {item.avg_score.toFixed(2)} +
+ {item.rating_count} votes
))} + {topPerformances.length === 0 && ( +
+ No ranked jams yet. Start rating performances! +
+ )}
- - + + - {/* Top Venues */} - - - - - Top Venues - - - -
- {topVenues.map((item, i) => ( -
-
- - {i + 1} - - + {/* TOP SHOWS CONTENT */} + +
+ {topShows.map((item, i) => ( +
+
+
{i + 1}
+
+ + {new Date(item.show.date).toLocaleDateString(undefined, { + year: 'numeric', month: 'long', day: 'numeric' + })} + +

+ {item.venue.name} • {item.venue.city}, {item.venue.state} +

+
+
+
+
+ + {item.avg_score.toFixed(2)} +
+
{item.review_count} ratings
+
+
+ ))} +
+
+ + {/* VENUES CONTENT */} + +
+ {topVenues.map((item, i) => ( +
+
+
+ {i + 1} +
+
+ {item.venue.name} -
-
- - {item.avg_score} - ({item.review_count}) +

+ {item.venue.city}, {item.venue.state} +

- ))} -
- - -
+ + + {item.avg_score.toFixed(2)} + +
+ ))} +
+ + + {/* USERS CONTENT */} + +
+ {topUsers.map((item, i) => ( + + + + U + +
User {item.profile.user_id}
+

Top Contributor

+ + {item.review_count} Reviews + +
+ ))} + {topUsers.length === 0 &&

No active users yet.

} +
+
+
) }