diff --git a/frontend/app/shows/page.tsx b/frontend/app/shows/page.tsx index 6e66d93..044bab8 100644 --- a/frontend/app/shows/page.tsx +++ b/frontend/app/shows/page.tsx @@ -2,14 +2,16 @@ import { useEffect, useState, Suspense, useMemo } from "react" import { getApiUrl } from "@/lib/api-config" -import { Loader2, Calendar } from "lucide-react" +import { Loader2, Calendar, Music2, Clock, Heart, Users, X } from "lucide-react" import { Skeleton } from "@/components/ui/skeleton" import { useSearchParams, useRouter, usePathname } from "next/navigation" import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" import Link from "next/link" -import { BandFilter } from "@/components/shows/band-filter" -import { FeedFilter } from "@/components/shows/feed-filter" +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs" import { DateGroupedList } from "@/components/shows/date-grouped-list" +import { VERTICALS } from "@/config/verticals" +import { Card, CardContent } from "@/components/ui/card" interface Show { id: number @@ -28,135 +30,341 @@ interface Show { } } +interface VerticalWithCount { + slug: string + name: string + showCount?: number +} + function ShowsContent() { const searchParams = useSearchParams() const router = useRouter() const pathname = usePathname() // Parse query params - const year = searchParams.get("year") const bandsParam = searchParams.get("bands") - const tiersParam = searchParams.get("tiers") + const viewParam = searchParams.get("view") || "recent" const initialBands = bandsParam ? bandsParam.split(",") : [] - const initialTiers = tiersParam ? tiersParam.split(",") : [] const [shows, setShows] = useState([]) + const [upcomingShows, setUpcomingShows] = useState([]) const [loading, setLoading] = useState(true) const [selectedBands, setSelectedBands] = useState(initialBands) - const [selectedTiers, setSelectedTiers] = useState(initialTiers) + const [activeView, setActiveView] = useState(viewParam) + const [bandCounts, setBandCounts] = useState>({}) + + // Fetch band counts on mount + useEffect(() => { + fetch(`${getApiUrl()}/verticals/`) + .then(res => res.json()) + .then(verticals => { + // We'll get counts from the shows data instead + }) + .catch(console.error) + }, []) // Update URL when filters change - const updateBandFilters = (bands: string[]) => { - setSelectedBands(bands) - updateUrl(bands, selectedTiers) - } - - const updateTierFilters = (tiers: string[]) => { - setSelectedTiers(tiers) - updateUrl(selectedBands, tiers) - } - - const updateUrl = (bands: string[], tiers: string[]) => { - const params = new URLSearchParams(searchParams.toString()) - + const updateFilters = (bands: string[], view: string) => { + const params = new URLSearchParams() if (bands.length > 0) { params.set("bands", bands.join(",")) - } else { - params.delete("bands") } - - if (tiers.length > 0) { - params.set("tiers", tiers.join(",")) - } else { - params.delete("tiers") + if (view !== "recent") { + params.set("view", view) } - - router.push(`${pathname}?${params.toString()}`) + const queryString = params.toString() + router.push(`${pathname}${queryString ? `?${queryString}` : ''}`) } + const toggleBand = (slug: string) => { + const newBands = selectedBands.includes(slug) + ? selectedBands.filter(s => s !== slug) + : [...selectedBands, slug] + setSelectedBands(newBands) + updateFilters(newBands, activeView) + } + + const clearBandFilters = () => { + setSelectedBands([]) + updateFilters([], activeView) + } + + const handleViewChange = (view: string) => { + setActiveView(view) + updateFilters(selectedBands, view) + } + + // Fetch shows based on view useEffect(() => { + if (activeView === "bands") return // No fetch needed for bands view + setLoading(true) const params = new URLSearchParams() - params.append("limit", "200") // Lower limit for initial partial view, or keep 2000 if needed but likely smaller is better if filtered - params.append("status", "past") + params.append("limit", "200") - if (year) params.append("year", year) + if (activeView === "recent") { + params.append("status", "past") + } else if (activeView === "upcoming") { + params.append("status", "upcoming") + } if (selectedBands.length > 0) { selectedBands.forEach(slug => params.append("vertical_slugs", slug)) } - if (selectedTiers.length > 0) { - selectedTiers.forEach(tier => params.append("tiers", tier)) - } - const url = `${getApiUrl()}/shows/?${params.toString()}` fetch(url) .then(res => res.json()) .then(data => { - // Backend might not sort perfectly if multiple bands (it sorts by offset usually, or default order). - // Currently backend read_shows does NO sorting (unless I missed it). - // read_shows only does offset/limit. - // So I should sort client side or add sort to backend. - // Assuming backend returns unsorted or DB order. const sorted = data.sort((a: Show, b: Show) => - new Date(b.date).getTime() - new Date(a.date).getTime() + activeView === "upcoming" + ? new Date(a.date).getTime() - new Date(b.date).getTime() + : new Date(b.date).getTime() - new Date(a.date).getTime() ) - setShows(sorted) + + // Calculate band counts from data + const counts: Record = {} + data.forEach((show: Show) => { + if (show.vertical?.slug) { + counts[show.vertical.slug] = (counts[show.vertical.slug] || 0) + 1 + } + }) + setBandCounts(counts) + + if (activeView === "upcoming") { + setUpcomingShows(sorted) + } else { + setShows(sorted) + } }) .catch(console.error) .finally(() => setLoading(false)) - }, [year, selectedBands, selectedTiers]) + }, [activeView, selectedBands]) + + // Get unique bands from current data for filter pills + const activeBandsInData = useMemo(() => { + const dataToCheck = activeView === "upcoming" ? upcomingShows : shows + const slugs = new Set(dataToCheck.map(s => s.vertical?.slug).filter(Boolean)) + return Array.from(slugs) as string[] + }, [shows, upcomingShows, activeView]) + + // Bands available for filtering + const availableBands = VERTICALS.filter(v => + activeBandsInData.includes(v.slug) || selectedBands.includes(v.slug) + ) + + return ( +
+ {/* Header */} +
+

Shows

+

+ Browse the complete archive across all bands. +

+
+ + {/* Tabs Navigation */} + +
+ + + + Recent + + + + By Band + + + + Upcoming + + + + My Feed + + +
+ + {/* Band Filter Pills - Only show on Recent and Upcoming */} + {(activeView === "recent" || activeView === "upcoming") && ( +
+ Filter: + + {selectedBands.length === 0 ? ( + + All Bands + + ) : ( + <> + {selectedBands.map(slug => { + const band = VERTICALS.find(v => v.slug === slug) + return ( + toggleBand(slug)} + > + {band?.name || slug} + + + ) + })} + + + )} + + {/* Add band buttons */} + {selectedBands.length === 0 && availableBands.length > 0 && ( +
+ {availableBands.slice(0, 5).map(band => ( + toggleBand(band.slug)} + > + {band.name} + {bandCounts[band.slug] && ( + + ({bandCounts[band.slug]}) + + )} + + ))} + {availableBands.length > 5 && ( + + +{availableBands.length - 5} more + + )} +
+ )} +
+ )} + + {/* Tab Content */} + + {loading ? ( + + ) : ( + + )} + + + + + + + + {loading ? ( + + ) : upcomingShows.length === 0 ? ( +
+ +

No upcoming shows announced

+

Check back later for new tour dates!

+
+ ) : ( + + )} +
+ + + + +
+
+ ) +} + +function BandsGrid() { + const [verticals, setVerticals] = useState([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetch(`${getApiUrl()}/verticals/`) + .then(res => res.json()) + .then(data => setVerticals(data)) + .catch(console.error) + .finally(() => setLoading(false)) + }, []) if (loading) { return ( -
-
- - -
- -
- {Array.from({ length: 12 }).map((_, i) => ( - - ))} -
+
+ {Array.from({ length: 8 }).map((_, i) => ( + + ))}
) } return ( -
-
-
-
-

Shows

-

- Browse the complete archive of performances. -

-
-
- - - - - -
+
+ {verticals.map(vertical => ( + + + +
+
+ +
+
+

{vertical.name}

+

+ {vertical.description?.slice(0, 50)}... +

+
+
+
+
+ + ))} +
+ ) +} + +function MyFeedPlaceholder() { + return ( +
+ +

Your Personal Feed

+

+ Follow your favorite bands to see a customized feed of shows tailored to your preferences. +

+
+ + + + + + +
+
+ ) +} + +function LoadingSkeleton() { + return ( +
+
+ +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))}
- -
) }