diff --git a/src/components/homepage/ContentTabs.tsx b/src/components/homepage/ContentTabs.tsx index e824148d..b09f8937 100644 --- a/src/components/homepage/ContentTabs.tsx +++ b/src/components/homepage/ContentTabs.tsx @@ -31,6 +31,10 @@ export function ContentTabs() { const [recentRides, setRecentRides] = useState([]); const [recentChanges, setRecentChanges] = useState([]); const [recentlyOpened, setRecentlyOpened] = useState>([]); + const [highestRatedParks, setHighestRatedParks] = useState([]); + const [highestRatedRides, setHighestRatedRides] = useState([]); + const [openingSoon, setOpeningSoon] = useState>([]); + const [closingSoon, setClosingSoon] = useState>([]); const [loading, setLoading] = useState(true); useEffect(() => { @@ -213,12 +217,94 @@ export function ContentTabs() { .sort((a, b) => new Date(b.opening_date).getTime() - new Date(a.opening_date).getTime()) .slice(0, 24); + // Highest Rated Parks + const { data: topRatedParks } = await supabase + .from('parks') + .select(`*, location:locations(*), operator:companies!parks_operator_id_fkey(*)`) + .gt('review_count', 0) + .order('average_rating', { ascending: false }) + .order('review_count', { ascending: false }) + .limit(12); + + // Highest Rated Rides + const { data: topRatedRides } = await supabase + .from('rides') + .select(`*, park:parks!inner(name, slug, location:locations(*))`) + .gt('review_count', 0) + .order('average_rating', { ascending: false }) + .order('review_count', { ascending: false }) + .limit(12); + + // Opening Soon (next 6 months) + const sixMonthsFromNow = new Date(); + sixMonthsFromNow.setMonth(sixMonthsFromNow.getMonth() + 6); + const futureThreshold = sixMonthsFromNow.toISOString().split('T')[0]; + + const { data: parksSoon } = await supabase + .from('parks') + .select(`*, location:locations(*), operator:companies!parks_operator_id_fkey(*)`) + .not('opening_date', 'is', null) + .gt('opening_date', new Date().toISOString().split('T')[0]) + .lte('opening_date', futureThreshold) + .in('status', ['under_construction', 'planned']) + .order('opening_date', { ascending: true }) + .limit(20); + + const { data: ridesSoon } = await supabase + .from('rides') + .select(`*, park:parks!inner(name, slug, location:locations(*))`) + .not('opening_date', 'is', null) + .gt('opening_date', new Date().toISOString().split('T')[0]) + .lte('opening_date', futureThreshold) + .in('status', ['under_construction', 'planned']) + .order('opening_date', { ascending: true }) + .limit(20); + + const combinedOpening = [ + ...(parksSoon || []).map(p => ({ ...p, entityType: 'park' as const })), + ...(ridesSoon || []).map(r => ({ ...r, entityType: 'ride' as const })) + ] + .sort((a, b) => new Date(a.opening_date).getTime() - new Date(b.opening_date).getTime()) + .slice(0, 24); + + // Closing Soon (next 6 months) + const { data: parksClosing } = await supabase + .from('parks') + .select(`*, location:locations(*), operator:companies!parks_operator_id_fkey(*)`) + .not('closing_date', 'is', null) + .gt('closing_date', new Date().toISOString().split('T')[0]) + .lte('closing_date', futureThreshold) + .eq('status', 'operating') + .order('closing_date', { ascending: true }) + .limit(20); + + const { data: ridesClosing } = await supabase + .from('rides') + .select(`*, park:parks!inner(name, slug, location:locations(*))`) + .not('closing_date', 'is', null) + .gt('closing_date', new Date().toISOString().split('T')[0]) + .lte('closing_date', futureThreshold) + .eq('status', 'operating') + .order('closing_date', { ascending: true }) + .limit(20); + + const combinedClosing = [ + ...(parksClosing || []).map(p => ({ ...p, entityType: 'park' as const })), + ...(ridesClosing || []).map(r => ({ ...r, entityType: 'ride' as const })) + ] + .sort((a, b) => new Date(a.closing_date).getTime() - new Date(b.closing_date).getTime()) + .slice(0, 24); + setTrendingParks(trending || []); setRecentParks(recent || []); setTrendingRides(trendingRidesData || []); setRecentRides(recentRidesData || []); setRecentChanges(allChanges); setRecentlyOpened(combinedOpened); + setHighestRatedParks(topRatedParks || []); + setHighestRatedRides(topRatedRides || []); + setOpeningSoon(combinedOpening); + setClosingSoon(combinedClosing); } catch (error: unknown) { logger.error('Failed to fetch content', { error: getErrorMessage(error) }); } finally { @@ -260,7 +346,7 @@ export function ContentTabs() {
- + Trending Parks @@ -279,6 +365,18 @@ export function ContentTabs() { Recently Opened + + Top Parks + + + Top Rides + + + Opening Soon + + + Closing Soon +
@@ -402,6 +500,110 @@ export function ContentTabs() { ))}
+ + +
+

Highest Rated Parks

+

Top-rated theme parks based on visitor reviews

+
+
+ {highestRatedParks.length > 0 ? ( +
+ {highestRatedParks.map((park) => ( + + ))} +
+ ) : ( +
+ No rated parks available yet. Be the first to leave a review! +
+ )} +
+ + +
+

Highest Rated Rides

+

Top-rated attractions based on rider reviews

+
+
+ {highestRatedRides.length > 0 ? ( +
+ {highestRatedRides.map((ride) => ( + + ))} +
+ ) : ( +
+ No rated rides available yet. Be the first to leave a review! +
+ )} +
+ + +
+

Opening Soon

+

Parks and rides opening in the next 6 months

+
+
+ {openingSoon.length > 0 ? ( +
+ {openingSoon.map((entity: any) => ( + entity.entityType === 'park' ? ( +
+ + + {new Date(entity.opening_date).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} + +
+ ) : ( +
+ + + {new Date(entity.opening_date).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} + +
+ ) + ))} +
+ ) : ( +
+ No parks or rides scheduled to open in the next 6 months. +
+ )} +
+ + +
+

Closing Soon

+

Last chance: Parks and rides closing in the next 6 months

+
+
+ {closingSoon.length > 0 ? ( +
+ {closingSoon.map((entity: any) => ( + entity.entityType === 'park' ? ( +
+ + + Closes {new Date(entity.closing_date).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} + +
+ ) : ( +
+ + + Closes {new Date(entity.closing_date).toLocaleDateString('en-US', { month: 'short', year: 'numeric' })} + +
+ ) + ))} +
+ ) : ( +
+ No parks or rides scheduled to close in the next 6 months. +
+ )} +