diff --git a/backend/routers/songs.py b/backend/routers/songs.py
index 326cb2d..a2cb920 100644
--- a/backend/routers/songs.py
+++ b/backend/routers/songs.py
@@ -110,6 +110,8 @@ def read_song(slug: str, session: Session = Depends(get_session)):
venue_name = "Unknown"
venue_city = ""
venue_state = ""
+ artist_name = None
+ artist_slug = None
show_date = datetime.now()
show_slug = None
@@ -120,6 +122,9 @@ def read_song(slug: str, session: Session = Depends(get_session)):
venue_name = p.show.venue.name
venue_city = p.show.venue.city
venue_state = p.show.venue.state
+ if p.show.vertical:
+ artist_name = p.show.vertical.name
+ artist_slug = p.show.vertical.slug
r_stats = rating_stats.get(p.id, {"avg": 0.0, "count": 0})
@@ -139,15 +144,22 @@ def read_song(slug: str, session: Session = Depends(get_session)):
venue_name=venue_name,
venue_city=venue_city,
venue_state=venue_state,
+ artist_name=artist_name,
+ artist_slug=artist_slug,
avg_rating=r_stats["avg"],
total_reviews=r_stats["count"]
))
+ # Calculate artist distribution
+ from collections import Counter
+ artist_dist = Counter(p.artist_name for p in perf_dtos if p.artist_name)
+
# Merge song data with stats
song_with_stats = SongReadWithStats(
**song.model_dump(),
**stats
)
+ song_with_stats.artist_distribution = artist_dist
song_with_stats.tags = tags
song_with_stats.performances = perf_dtos
return song_with_stats
diff --git a/backend/schemas.py b/backend/schemas.py
index 1799693..814320f 100644
--- a/backend/schemas.py
+++ b/backend/schemas.py
@@ -135,6 +135,8 @@ class PerformanceReadWithShow(PerformanceRead):
venue_name: str
venue_city: str
venue_state: Optional[str] = None
+ artist_name: Optional[str] = None
+ artist_slug: Optional[str] = None
avg_rating: Optional[float] = 0.0
total_reviews: Optional[int] = 0
@@ -143,6 +145,7 @@ class SongReadWithStats(SongRead):
gap: int
last_played: Optional[datetime] = None
set_breakdown: Dict[str, int] = {}
+ artist_distribution: Dict[str, int] = {}
performances: List[PerformanceReadWithShow] = []
class PerformanceDetailRead(PerformanceRead):
diff --git a/frontend/app/songs/[slug]/page.tsx b/frontend/app/songs/[slug]/page.tsx
index 7308d17..637f17f 100644
--- a/frontend/app/songs/[slug]/page.tsx
+++ b/frontend/app/songs/[slug]/page.tsx
@@ -100,60 +100,95 @@ export default async function SongDetailPage({ params }: { params: Promise<{ slu
-
-
-
- Times Played
-
-
-
-
- {song.times_played}
-
-
-
-
-
- Gap (Shows)
-
-
-
-
- {song.gap}
-
-
-
-
-
- Last Played
-
-
-
-
- {song.last_played ? new Date(song.last_played).toLocaleDateString() : "Never"}
-
-
-
-
-
- {/* Set Breakdown */}
- {song.set_breakdown && Object.keys(song.set_breakdown).length > 0 && (
-
-
- Set Distribution
-
-
-
- {Object.entries(song.set_breakdown).sort((a, b) => (b[1] as number) - (a[1] as number)).map(([set, count]) => (
-
-
{count as number}
-
{set}
+
+ {/* Left Sidebar: Stats & Charts */}
+
+ {/* Basic Stats Grid - Compact */}
+
+
+
+ Times Played
+
+
+
- ))}
-
-
-
- )}
+
+
+
+
+ Gap
+
+
+
+
+ {song.gap}
+
+
+
+
+
+ Last Played
+
+
+
+
+ {song.last_played ? new Date(song.last_played).toLocaleDateString(undefined, {
+ weekday: 'long',
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric'
+ }) : "Never"}
+
+
+
+
+
+ {/* Most Played By */}
+ {song.artist_distribution && (
+
+ )}
+
+ {/* Set Breakdown */}
+ {song.set_breakdown && Object.keys(song.set_breakdown).length > 0 && (
+
+
+ Set Distribution
+
+
+
+ {Object.entries(song.set_breakdown).sort((a, b) => (b[1] as number) - (a[1] as number)).map(([set, count]) => (
+
+ {set}
+ {count as number}
+
+ ))}
+
+
+
+ )}
+
+
+ {/* Right Content: Performance History */}
+
+
+
+ {/* Song Evolution (moved to bottom) */}
+
+
+
+
+
+
+
+
+
+
{/* Heady Version Section */}
{headyVersions.length > 0 && (
@@ -282,20 +317,7 @@ export default async function SongDetailPage({ params }: { params: Promise<{ slu
)}
-
- {/* Performance List Component (Handles Client Sorting) */}
-
-
-
-
-
-
-
-
-
-
-
)
}
diff --git a/frontend/components/songs/most-played-by-card.tsx b/frontend/components/songs/most-played-by-card.tsx
new file mode 100644
index 0000000..7c00438
--- /dev/null
+++ b/frontend/components/songs/most-played-by-card.tsx
@@ -0,0 +1,38 @@
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Trophy } from "lucide-react"
+
+interface MostPlayedByProps {
+ distribution: Record
+}
+
+export function MostPlayedByCard({ distribution }: MostPlayedByProps) {
+ if (!distribution || Object.keys(distribution).length === 0) return null
+
+ // Sort entries by count descending
+ const sortedEntries = Object.entries(distribution).sort((a, b) => b[1] - a[1])
+
+ return (
+
+
+
+
+ Most Played By
+
+
+
+
+ {sortedEntries.map(([artist, count], index) => (
+
+
+ {artist}
+
+
+ {count}
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/frontend/components/songs/performance-list.tsx b/frontend/components/songs/performance-list.tsx
index bf132c5..2c6df7a 100644
--- a/frontend/components/songs/performance-list.tsx
+++ b/frontend/components/songs/performance-list.tsx
@@ -21,6 +21,8 @@ export interface Performance {
venue_name: string
venue_city: string
venue_state: string | null
+ artist_name?: string
+ artist_slug?: string
avg_rating: number
total_reviews: number
youtube_link?: string | null
@@ -86,9 +88,14 @@ export function PerformanceList({ performances }: PerformanceListProps) {
>
+ {perf.artist_name && (
+
+ {perf.artist_name}
+
+ )}
{new Date(perf.show_date).toLocaleDateString(undefined, {
year: 'numeric',