import { useState, useEffect, useMemo } from "react"; import { Header } from "@/components/layout/Header"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { cn } from "@/lib/utils"; import { Collapsible, CollapsibleContent } from "@/components/ui/collapsible"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { MapPin, Grid3X3, List, Map, Filter, SortAsc, Search, ChevronDown, Sliders, X, FerrisWheel, Plus, PanelLeftClose, PanelLeftOpen, } from "lucide-react"; import { Park } from "@/types/database"; import { supabase } from "@/lib/supabaseClient"; import { useNavigate } from "react-router-dom"; import { ParkFilters } from "@/components/parks/ParkFilters"; import { ParkGridView } from "@/components/parks/ParkGridView"; import { ParkListView } from "@/components/parks/ParkListView"; import { ParkSearch } from "@/components/parks/ParkSearch"; import { ParkSortOptions } from "@/components/parks/ParkSortOptions"; import { useToast } from "@/hooks/use-toast"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { ParkForm } from "@/components/admin/ParkForm"; import { useAuth } from "@/hooks/useAuth"; import { useUserRole } from "@/hooks/useUserRole"; import { useAuthModal } from "@/hooks/useAuthModal"; import { useOpenGraph } from "@/hooks/useOpenGraph"; import { useParks } from "@/hooks/parks/useParks"; import { Pagination } from "@/components/common/Pagination"; import { SubmissionErrorBoundary } from "@/components/error/SubmissionErrorBoundary"; export interface FilterState { search: string; parkType: string[]; status: string[]; country: string[]; states?: string[]; cities?: string[]; operators?: string[]; propertyOwners?: string[]; minRating: number; maxRating: number; minRides: number; maxRides: number; minCoasters?: number; maxCoasters?: number; minReviews?: number; maxReviews?: number; openingYearStart: number | null; openingYearEnd: number | null; openingDateFrom?: string | null; openingDateTo?: string | null; } export interface SortState { field: string; direction: "asc" | "desc"; } const initialFilters: FilterState = { search: "", parkType: [], status: [], country: [], states: [], cities: [], operators: [], propertyOwners: [], minRating: 0, maxRating: 5, minRides: 0, maxRides: 1000, minCoasters: 0, maxCoasters: 100, minReviews: 0, maxReviews: 1000, openingYearStart: null, openingYearEnd: null, openingDateFrom: null, openingDateTo: null, }; const initialSort: SortState = { field: "name", direction: "asc", }; export default function Parks() { const [filters, setFilters] = useState(initialFilters); const [sort, setSort] = useState(initialSort); const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); const [showFilters, setShowFilters] = useState(false); const [isAddParkModalOpen, setIsAddParkModalOpen] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { const saved = localStorage.getItem("parks-sidebar-collapsed"); return saved ? JSON.parse(saved) : false; }); const navigate = useNavigate(); const { toast } = useToast(); const { user } = useAuth(); const { isModerator } = useUserRole(); const { requireAuth } = useAuthModal(); // Use TanStack Query hook for data fetching with caching const { data: parks = [], isLoading: loading, error } = useParks(); useEffect(() => { localStorage.setItem("parks-sidebar-collapsed", JSON.stringify(sidebarCollapsed)); }, [sidebarCollapsed]); // Show error toast if query fails useEffect(() => { if (error) { toast({ variant: "destructive", title: "Error loading parks", description: error instanceof Error ? error.message : "Failed to load parks", }); } }, [error, toast]); const filteredAndSortedParks = useMemo(() => { let filtered = parks.filter((park) => { // Search filter if (filters.search) { const searchTerm = filters.search.toLowerCase(); const matchesSearch = park.name.toLowerCase().includes(searchTerm) || park.description?.toLowerCase().includes(searchTerm) || park.location?.city?.toLowerCase().includes(searchTerm) || park.location?.country?.toLowerCase().includes(searchTerm) || park.location?.state_province?.toLowerCase().includes(searchTerm); if (!matchesSearch) return false; } // Park type filter if (filters.parkType.length > 0 && !filters.parkType.includes(park.park_type)) { return false; } // Status filter if (filters.status.length > 0 && !filters.status.includes(park.status)) { return false; } // Country filter if (filters.country.length > 0 && !filters.country.includes(park.location?.country || "")) { return false; } // States filter if (filters.states && filters.states.length > 0) { if (!park.location?.state_province || !filters.states.includes(park.location.state_province)) { return false; } } // Cities filter if (filters.cities && filters.cities.length > 0) { if (!park.location?.city || !filters.cities.includes(park.location.city)) { return false; } } // Operators filter if (filters.operators && filters.operators.length > 0) { if (!park.operator?.id || !filters.operators.includes(park.operator.id)) { return false; } } // Property owners filter if (filters.propertyOwners && filters.propertyOwners.length > 0) { if (!park.property_owner?.id || !filters.propertyOwners.includes(park.property_owner.id)) { return false; } } // Rating filter const rating = park.average_rating || 0; if (rating < filters.minRating || rating > filters.maxRating) { return false; } // Ride count filter const rideCount = park.ride_count || 0; if (rideCount < filters.minRides || rideCount > filters.maxRides) { return false; } // Coaster count filter if (filters.minCoasters !== undefined && filters.maxCoasters !== undefined) { const coasterCount = park.coaster_count || 0; if (coasterCount < filters.minCoasters || coasterCount > filters.maxCoasters) { return false; } } // Review count filter if (filters.minReviews !== undefined && filters.maxReviews !== undefined) { const reviewCount = park.review_count || 0; if (reviewCount < filters.minReviews || reviewCount > filters.maxReviews) { return false; } } // Opening date filter (timezone-independent) if (filters.openingDateFrom || filters.openingDateTo || filters.openingYearStart || filters.openingYearEnd) { if (!park.opening_date) { return false; } // Full date filtering (if date range is set) if (filters.openingDateFrom && park.opening_date < filters.openingDateFrom) { return false; } if (filters.openingDateTo && park.opening_date > filters.openingDateTo) { return false; } // Year-only filtering (for backward compatibility) const openingYear = parseInt(park.opening_date.split("-")[0]); if (filters.openingYearStart && openingYear < filters.openingYearStart) { return false; } if (filters.openingYearEnd && openingYear > filters.openingYearEnd) { return false; } } return true; }); // Apply sorting filtered.sort((a, b) => { let aValue: any, bValue: any; switch (sort.field) { case "name": aValue = a.name; bValue = b.name; break; case "rating": aValue = a.average_rating || 0; bValue = b.average_rating || 0; break; case "rides": aValue = a.ride_count || 0; bValue = b.ride_count || 0; break; case "coasters": aValue = a.coaster_count || 0; bValue = b.coaster_count || 0; break; case "reviews": aValue = a.review_count || 0; bValue = b.review_count || 0; break; case "opening": aValue = a.opening_date ? new Date(a.opening_date).getTime() : 0; bValue = b.opening_date ? new Date(b.opening_date).getTime() : 0; break; default: aValue = a.name; bValue = b.name; } if (typeof aValue === "string" && typeof bValue === "string") { const result = aValue.localeCompare(bValue); return sort.direction === "asc" ? result : -result; } const result = aValue - bValue; return sort.direction === "asc" ? result : -result; }); return filtered; }, [parks, filters, sort]); const generateDescription = () => { if (!parks.length) return "Browse theme parks worldwide on ThrillWiki"; const activeFilters: string[] = []; if (filters.country.length === 1) activeFilters.push(`in ${filters.country[0]!}`); if (filters.parkType.length > 0) activeFilters.push(...filters.parkType); if (filters.status.length > 0) activeFilters.push(...filters.status); if (activeFilters.length > 0) { return `Browse ${filteredAndSortedParks.length} ${activeFilters.join(" ")} theme parks`; } return `Browse ${parks.length} theme parks worldwide`; }; useOpenGraph({ title: "Parks - ThrillWiki", description: generateDescription(), imageUrl: filteredAndSortedParks[0]?.banner_image_url ?? undefined, imageId: filteredAndSortedParks[0]?.banner_image_id ?? undefined, type: "website", }); const activeFilterCount = useMemo(() => { let count = 0; if (filters.search) count++; if (filters.parkType.length > 0) count++; if (filters.status.length > 0) count++; if (filters.country.length > 0) count++; if (filters.minRating > 0 || filters.maxRating < 5) count++; if (filters.minRides > 0 || filters.maxRides < 1000) count++; if (filters.openingYearStart || filters.openingYearEnd) count++; return count; }, [filters]); const clearAllFilters = () => { setFilters(initialFilters); setSort(initialSort); }; const handleParkClick = (park: Park) => { navigate(`/parks/${park.slug}`); }; // Pagination for display const ITEMS_PER_PAGE = 24; const paginatedParks = useMemo(() => { const start = (currentPage - 1) * ITEMS_PER_PAGE; const end = start + ITEMS_PER_PAGE; return filteredAndSortedParks.slice(start, end); }, [filteredAndSortedParks, currentPage]); const totalPages = Math.ceil(filteredAndSortedParks.length / ITEMS_PER_PAGE); // Reset to page 1 when filters change useEffect(() => { setCurrentPage(1); }, [filters, sort]); const handleParkSubmit = async (parkData: any) => { try { const { submitParkCreation } = await import("@/lib/entitySubmissionHelpers"); await submitParkCreation(parkData, user!.id); toast({ title: "Park Submitted", description: "Your park submission has been sent for moderation review.", }); setIsAddParkModalOpen(false); } catch (error) { toast({ title: "Submission Failed", description: error instanceof Error ? error.message : "Failed to submit park.", variant: "destructive", }); } }; if (loading) { return (
{[...Array(12)].map((_, i) => (
))}
); } return (
{/* Page Header */}

Parks

Discover amazing theme parks, amusement parks, and attractions worldwide

{filteredAndSortedParks.length} parks {parks.reduce((sum, park) => sum + (park.ride_count || 0), 0)} total rides {parks.reduce((sum, park) => sum + (park.coaster_count || 0), 0)} coasters
{activeFilterCount > 0 && ( )}
{/* Search and Controls */}
{/* Desktop: Filter toggle on the left */} {/* Search bar takes remaining space */}
setFilters((prev) => ({ ...prev, search }))} />
{/* Sort controls - more compact */}
{/* Mobile filter toggle */} setViewMode(v as "grid" | "list")} className="hidden md:inline-flex" >
{/* Mobile Filters */}
{/* Main Content Area with Sidebar */}
{/* Desktop Filter Sidebar */} {!sidebarCollapsed && ( )} {/* Results Area */}
{filteredAndSortedParks.length > 0 ? (
{viewMode === "grid" ? ( ) : ( )}
) : (

No parks found

Try adjusting your search terms or filters

)}
{/* Add Park Modal */} Add New Park Add a new park to the database. Your submission will be reviewed before being published. setIsAddParkModalOpen(false)} isEditing={false} />
); }