diff --git a/src/App.tsx b/src/App.tsx index 2027bce0..5fdb92ca 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,12 +14,17 @@ import RideDetail from "./pages/RideDetail"; import Rides from "./pages/Rides"; import Manufacturers from "./pages/Manufacturers"; import ManufacturerDetail from "./pages/ManufacturerDetail"; +import ManufacturerRides from "./pages/ManufacturerRides"; +import ManufacturerModels from "./pages/ManufacturerModels"; import Designers from "./pages/Designers"; import DesignerDetail from "./pages/DesignerDetail"; +import DesignerRides from "./pages/DesignerRides"; import ParkOwners from "./pages/ParkOwners"; import PropertyOwnerDetail from "./pages/PropertyOwnerDetail"; +import OwnerParks from "./pages/OwnerParks"; import Operators from "./pages/Operators"; import OperatorDetail from "./pages/OperatorDetail"; +import OperatorParks from "./pages/OperatorParks"; import Auth from "./pages/Auth"; import Profile from "./pages/Profile"; import UserSettings from "./pages/UserSettings"; @@ -51,12 +56,17 @@ function AppContent() { } /> } /> } /> + } /> + } /> } /> } /> + } /> } /> } /> + } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/rides/RideModelCard.tsx b/src/components/rides/RideModelCard.tsx new file mode 100644 index 00000000..2bca5c26 --- /dev/null +++ b/src/components/rides/RideModelCard.tsx @@ -0,0 +1,84 @@ +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { FerrisWheel } from 'lucide-react'; +import { RideModel } from '@/types/database'; +import { useNavigate } from 'react-router-dom'; + +interface RideModelCardProps { + model: RideModel; + manufacturerSlug: string; +} + +export function RideModelCard({ model, manufacturerSlug }: RideModelCardProps) { + const navigate = useNavigate(); + + const formatCategory = (category: string) => { + return category.split('_').map(word => + word.charAt(0).toUpperCase() + word.slice(1) + ).join(' '); + }; + + const formatRideType = (type: string) => { + return type.split('_').map(word => + word.charAt(0).toUpperCase() + word.slice(1) + ).join(' '); + }; + + const rideCount = (model as any).rides?.[0]?.count || 0; + + return ( + +
+ {((model as any).card_image_url || (model as any).card_image_id) ? ( + {model.name} + ) : ( +
+ +
+ )} +
+ + +
+

+ {model.name} +

+ {model.description && ( +

+ {model.description} +

+ )} +
+ +
+ + {formatCategory(model.category)} + + + {formatRideType(model.ride_type)} + +
+ +
+ + {rideCount} {rideCount === 1 ? 'ride' : 'rides'} + + +
+
+
+ ); +} diff --git a/src/pages/DesignerDetail.tsx b/src/pages/DesignerDetail.tsx index a1f44798..b7026f31 100644 --- a/src/pages/DesignerDetail.tsx +++ b/src/pages/DesignerDetail.tsx @@ -240,7 +240,18 @@ export default function DesignerDetail() { -

Rides designed by {designer.name} will be displayed here.

+
+

Rides

+ +
+

+ View all rides designed by {designer.name} +

diff --git a/src/pages/DesignerRides.tsx b/src/pages/DesignerRides.tsx new file mode 100644 index 00000000..e73405d2 --- /dev/null +++ b/src/pages/DesignerRides.tsx @@ -0,0 +1,252 @@ +import { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Header } from '@/components/layout/Header'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { ArrowLeft, Filter, SlidersHorizontal, FerrisWheel } from 'lucide-react'; +import { Ride, Company } from '@/types/database'; +import { supabase } from '@/integrations/supabase/client'; +import { RideCard } from '@/components/rides/RideCard'; +import { AutocompleteSearch } from '@/components/search/AutocompleteSearch'; + +export default function DesignerRides() { + const { designerSlug } = useParams<{ designerSlug: string }>(); + const navigate = useNavigate(); + const [designer, setDesigner] = useState(null); + const [rides, setRides] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [sortBy, setSortBy] = useState('name'); + const [filterCategory, setFilterCategory] = useState('all'); + const [filterStatus, setFilterStatus] = useState('all'); + + useEffect(() => { + if (designerSlug) { + fetchData(); + } + }, [designerSlug, sortBy, filterCategory, filterStatus]); + + const fetchData = async () => { + try { + // Fetch designer + const { data: designerData, error: designerError } = await supabase + .from('companies') + .select('*') + .eq('slug', designerSlug) + .eq('company_type', 'designer') + .maybeSingle(); + + if (designerError) throw designerError; + setDesigner(designerData); + + if (designerData) { + // Fetch rides designed by this company + let query = supabase + .from('rides') + .select(` + *, + park:parks!inner(name, slug, location:locations(*)), + manufacturer:companies!rides_manufacturer_id_fkey(*) + `) + .eq('designer_id', designerData.id); + + if (filterCategory !== 'all') { + query = query.eq('category', filterCategory); + } + if (filterStatus !== 'all') { + query = query.eq('status', filterStatus); + } + + switch (sortBy) { + case 'rating': + query = query.order('average_rating', { ascending: false }); + break; + case 'speed': + query = query.order('max_speed_kmh', { ascending: false, nullsFirst: false }); + break; + case 'height': + query = query.order('max_height_meters', { ascending: false, nullsFirst: false }); + break; + case 'reviews': + query = query.order('review_count', { ascending: false }); + break; + default: + query = query.order('name'); + } + + const { data: ridesData, error: ridesError } = await query; + if (ridesError) throw ridesError; + setRides(ridesData || []); + } + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + }; + + const filteredRides = rides.filter(ride => + ride.name.toLowerCase().includes(searchQuery.toLowerCase()) || + ride.park?.name?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const categories = [ + { value: 'all', label: 'All Categories' }, + { value: 'roller_coaster', label: 'Roller Coasters' }, + { value: 'flat_ride', label: 'Flat Rides' }, + { value: 'water_ride', label: 'Water Rides' }, + { value: 'dark_ride', label: 'Dark Rides' }, + { value: 'kiddie_ride', label: 'Kiddie Rides' }, + { value: 'transportation', label: 'Transportation' } + ]; + + const statusOptions = [ + { value: 'all', label: 'All Status' }, + { value: 'operating', label: 'Operating' }, + { value: 'seasonal', label: 'Seasonal' }, + { value: 'under_construction', label: 'Under Construction' }, + { value: 'closed', label: 'Closed' } + ]; + + if (loading) { + return ( +
+
+
+
+
+
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+
+
+
+ ); + } + + if (!designer) { + return ( +
+
+
+
+

Designer Not Found

+ +
+
+
+ ); + } + + return ( +
+
+ +
+
+ +
+ +
+
+ +

Rides by {designer.name}

+
+

+ Explore all rides designed by {designer.name} +

+ +
+ + {filteredRides.length} rides + + + {rides.filter(r => r.category === 'roller_coaster').length} coasters + +
+
+ +
+
+
+ setSearchQuery(query)} + showRecentSearches={false} + /> +
+
+ + + + + +
+
+
+ + {filteredRides.length > 0 ? ( +
+ {filteredRides.map((ride) => ( + + ))} +
+ ) : ( +
+ +

No rides found

+

+ {designer.name} hasn't designed any rides matching your criteria +

+
+ )} +
+
+ ); +} diff --git a/src/pages/ManufacturerDetail.tsx b/src/pages/ManufacturerDetail.tsx index 18d12b62..3ffe920a 100644 --- a/src/pages/ManufacturerDetail.tsx +++ b/src/pages/ManufacturerDetail.tsx @@ -220,9 +220,10 @@ export default function ManufacturerDetail() { {/* Tabs */} - + Overview Rides + Models Photos @@ -242,7 +243,37 @@ export default function ManufacturerDetail() { -

Rides manufactured by {manufacturer.name} will be displayed here.

+
+

Rides

+ +
+

+ View all rides manufactured by {manufacturer.name} +

+
+
+
+ + + + +
+

Models

+ +
+

+ View all ride models by {manufacturer.name} +

diff --git a/src/pages/ManufacturerModels.tsx b/src/pages/ManufacturerModels.tsx new file mode 100644 index 00000000..c83f34cf --- /dev/null +++ b/src/pages/ManufacturerModels.tsx @@ -0,0 +1,212 @@ +import { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Header } from '@/components/layout/Header'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { ArrowLeft, Filter, SlidersHorizontal, FerrisWheel } from 'lucide-react'; +import { RideModel, Company } from '@/types/database'; +import { supabase } from '@/integrations/supabase/client'; +import { RideModelCard } from '@/components/rides/RideModelCard'; +import { AutocompleteSearch } from '@/components/search/AutocompleteSearch'; + +export default function ManufacturerModels() { + const { manufacturerSlug } = useParams<{ manufacturerSlug: string }>(); + const navigate = useNavigate(); + const [manufacturer, setManufacturer] = useState(null); + const [models, setModels] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [sortBy, setSortBy] = useState('name'); + const [filterCategory, setFilterCategory] = useState('all'); + + useEffect(() => { + if (manufacturerSlug) { + fetchData(); + } + }, [manufacturerSlug, sortBy, filterCategory]); + + const fetchData = async () => { + try { + // Fetch manufacturer + const { data: manufacturerData, error: manufacturerError } = await supabase + .from('companies') + .select('*') + .eq('slug', manufacturerSlug) + .eq('company_type', 'manufacturer') + .maybeSingle(); + + if (manufacturerError) throw manufacturerError; + setManufacturer(manufacturerData); + + if (manufacturerData) { + // Fetch ride models with ride count + let query = supabase + .from('ride_models') + .select(` + *, + rides:rides(count) + `) + .eq('manufacturer_id', manufacturerData.id); + + if (filterCategory !== 'all') { + query = query.eq('category', filterCategory); + } + + switch (sortBy) { + case 'name': + query = query.order('name'); + break; + default: + query = query.order('name'); + } + + const { data: modelsData, error: modelsError } = await query; + if (modelsError) throw modelsError; + setModels((modelsData || []) as any); + } + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + }; + + const filteredModels = models.filter(model => + model.name.toLowerCase().includes(searchQuery.toLowerCase()) || + model.description?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const categories = [ + { value: 'all', label: 'All Categories' }, + { value: 'roller_coaster', label: 'Roller Coasters' }, + { value: 'flat_ride', label: 'Flat Rides' }, + { value: 'water_ride', label: 'Water Rides' }, + { value: 'dark_ride', label: 'Dark Rides' }, + { value: 'kiddie_ride', label: 'Kiddie Rides' }, + { value: 'transportation', label: 'Transportation' } + ]; + + if (loading) { + return ( +
+
+
+
+
+
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+
+
+
+ ); + } + + if (!manufacturer) { + return ( +
+
+
+
+

Manufacturer Not Found

+ +
+
+
+ ); + } + + return ( +
+
+ +
+
+ +
+ +
+
+ +

Models by {manufacturer.name}

+
+

+ Explore all ride models manufactured by {manufacturer.name} +

+ + + {filteredModels.length} models + +
+ +
+
+
+ setSearchQuery(query)} + showRecentSearches={false} + /> +
+
+ + + +
+
+
+ + {filteredModels.length > 0 ? ( +
+ {filteredModels.map((model) => ( + + ))} +
+ ) : ( +
+ +

No models found

+

+ {manufacturer.name} doesn't have any models matching your criteria +

+
+ )} +
+
+ ); +} diff --git a/src/pages/ManufacturerRides.tsx b/src/pages/ManufacturerRides.tsx new file mode 100644 index 00000000..b3241cab --- /dev/null +++ b/src/pages/ManufacturerRides.tsx @@ -0,0 +1,252 @@ +import { useState, useEffect } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Header } from '@/components/layout/Header'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { ArrowLeft, Filter, SlidersHorizontal, FerrisWheel } from 'lucide-react'; +import { Ride, Company } from '@/types/database'; +import { supabase } from '@/integrations/supabase/client'; +import { RideCard } from '@/components/rides/RideCard'; +import { AutocompleteSearch } from '@/components/search/AutocompleteSearch'; + +export default function ManufacturerRides() { + const { manufacturerSlug } = useParams<{ manufacturerSlug: string }>(); + const navigate = useNavigate(); + const [manufacturer, setManufacturer] = useState(null); + const [rides, setRides] = useState([]); + const [loading, setLoading] = useState(true); + const [searchQuery, setSearchQuery] = useState(''); + const [sortBy, setSortBy] = useState('name'); + const [filterCategory, setFilterCategory] = useState('all'); + const [filterStatus, setFilterStatus] = useState('all'); + + useEffect(() => { + if (manufacturerSlug) { + fetchData(); + } + }, [manufacturerSlug, sortBy, filterCategory, filterStatus]); + + const fetchData = async () => { + try { + // Fetch manufacturer + const { data: manufacturerData, error: manufacturerError } = await supabase + .from('companies') + .select('*') + .eq('slug', manufacturerSlug) + .eq('company_type', 'manufacturer') + .maybeSingle(); + + if (manufacturerError) throw manufacturerError; + setManufacturer(manufacturerData); + + if (manufacturerData) { + // Fetch rides manufactured by this company + let query = supabase + .from('rides') + .select(` + *, + park:parks!inner(name, slug, location:locations(*)), + manufacturer:companies!rides_manufacturer_id_fkey(*) + `) + .eq('manufacturer_id', manufacturerData.id); + + if (filterCategory !== 'all') { + query = query.eq('category', filterCategory); + } + if (filterStatus !== 'all') { + query = query.eq('status', filterStatus); + } + + switch (sortBy) { + case 'rating': + query = query.order('average_rating', { ascending: false }); + break; + case 'speed': + query = query.order('max_speed_kmh', { ascending: false, nullsFirst: false }); + break; + case 'height': + query = query.order('max_height_meters', { ascending: false, nullsFirst: false }); + break; + case 'reviews': + query = query.order('review_count', { ascending: false }); + break; + default: + query = query.order('name'); + } + + const { data: ridesData, error: ridesError } = await query; + if (ridesError) throw ridesError; + setRides(ridesData || []); + } + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + }; + + const filteredRides = rides.filter(ride => + ride.name.toLowerCase().includes(searchQuery.toLowerCase()) || + ride.park?.name?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + const categories = [ + { value: 'all', label: 'All Categories' }, + { value: 'roller_coaster', label: 'Roller Coasters' }, + { value: 'flat_ride', label: 'Flat Rides' }, + { value: 'water_ride', label: 'Water Rides' }, + { value: 'dark_ride', label: 'Dark Rides' }, + { value: 'kiddie_ride', label: 'Kiddie Rides' }, + { value: 'transportation', label: 'Transportation' } + ]; + + const statusOptions = [ + { value: 'all', label: 'All Status' }, + { value: 'operating', label: 'Operating' }, + { value: 'seasonal', label: 'Seasonal' }, + { value: 'under_construction', label: 'Under Construction' }, + { value: 'closed', label: 'Closed' } + ]; + + if (loading) { + return ( +
+
+
+
+
+
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+
+
+
+ ); + } + + if (!manufacturer) { + return ( +
+
+
+
+

Manufacturer Not Found

+ +
+
+
+ ); + } + + return ( +
+
+ +
+
+ +
+ +
+
+ +

Rides by {manufacturer.name}

+
+

+ Explore all rides manufactured by {manufacturer.name} +

+ +
+ + {filteredRides.length} rides + + + {rides.filter(r => r.category === 'roller_coaster').length} coasters + +
+
+ +
+
+
+ setSearchQuery(query)} + showRecentSearches={false} + /> +
+
+ + + + + +
+
+
+ + {filteredRides.length > 0 ? ( +
+ {filteredRides.map((ride) => ( + + ))} +
+ ) : ( +
+ +

No rides found

+

+ {manufacturer.name} hasn't manufactured any rides matching your criteria +

+
+ )} +
+
+ ); +} diff --git a/src/pages/OperatorDetail.tsx b/src/pages/OperatorDetail.tsx index 203cd054..05ff6b46 100644 --- a/src/pages/OperatorDetail.tsx +++ b/src/pages/OperatorDetail.tsx @@ -240,7 +240,18 @@ export default function OperatorDetail() { -

Parks operated by {operator.name} will be displayed here.

+
+

Parks

+ +
+

+ View all parks operated by {operator.name} +

diff --git a/src/pages/OperatorParks.tsx b/src/pages/OperatorParks.tsx new file mode 100644 index 00000000..6d421802 --- /dev/null +++ b/src/pages/OperatorParks.tsx @@ -0,0 +1,278 @@ +import { useState, useEffect, useMemo } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Header } from '@/components/layout/Header'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Card, CardContent } from '@/components/ui/card'; +import { ArrowLeft, MapPin, Filter, FerrisWheel } from 'lucide-react'; +import { Park, Company } from '@/types/database'; +import { supabase } from '@/integrations/supabase/client'; +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 { ParkFilters } from '@/components/parks/ParkFilters'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Grid3X3, List } from 'lucide-react'; +import { FilterState, SortState } from './Parks'; + +const initialFilters: FilterState = { + search: '', + parkType: 'all', + status: 'all', + country: 'all', + minRating: 0, + maxRating: 5, + minRides: 0, + maxRides: 1000, + openingYearStart: null, + openingYearEnd: null, +}; + +const initialSort: SortState = { + field: 'name', + direction: 'asc' +}; + +export default function OperatorParks() { + const { operatorSlug } = useParams<{ operatorSlug: string }>(); + const navigate = useNavigate(); + const [operator, setOperator] = useState(null); + const [parks, setParks] = useState([]); + const [loading, setLoading] = useState(true); + const [filters, setFilters] = useState(initialFilters); + const [sort, setSort] = useState(initialSort); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); + const [showFilters, setShowFilters] = useState(false); + + useEffect(() => { + if (operatorSlug) { + fetchData(); + } + }, [operatorSlug]); + + const fetchData = async () => { + try { + // Fetch operator + const { data: operatorData, error: operatorError } = await supabase + .from('companies') + .select('*') + .eq('slug', operatorSlug) + .eq('company_type', 'operator') + .maybeSingle(); + + if (operatorError) throw operatorError; + setOperator(operatorData); + + if (operatorData) { + // Fetch parks operated by this operator + const { data: parksData, error: parksError } = await supabase + .from('parks') + .select(` + *, + location:locations(*), + operator:companies!parks_operator_id_fkey(*), + property_owner:companies!parks_property_owner_id_fkey(*) + `) + .eq('operator_id', operatorData.id) + .order('name'); + + if (parksError) throw parksError; + setParks(parksData || []); + } + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + }; + + const filteredAndSortedParks = useMemo(() => { + let filtered = parks.filter(park => { + 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); + if (!matchesSearch) return false; + } + if (filters.parkType !== 'all' && park.park_type !== filters.parkType) return false; + if (filters.status !== 'all' && park.status !== filters.status) return false; + if (filters.country !== 'all' && park.location?.country !== filters.country) return false; + + const rating = park.average_rating || 0; + if (rating < filters.minRating || rating > filters.maxRating) return false; + + const rideCount = park.ride_count || 0; + if (rideCount < filters.minRides || rideCount > filters.maxRides) return false; + + return true; + }); + + 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; + default: + aValue = a.name; + bValue = b.name; + } + + if (typeof aValue === 'string' && typeof bValue === 'string') { + return sort.direction === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); + } + return sort.direction === 'asc' ? aValue - bValue : bValue - aValue; + }); + + return filtered; + }, [parks, filters, sort]); + + if (loading) { + return ( +
+
+
+
+
+
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+
+
+
+ ); + } + + if (!operator) { + return ( +
+
+
+
+

Operator Not Found

+ +
+
+
+ ); + } + + return ( +
+
+ +
+
+ +
+ +
+
+ +

Parks Operated by {operator.name}

+
+

+ Explore all theme parks operated by {operator.name} +

+ + + {filteredAndSortedParks.length} parks + +
+ +
+
+
+ setFilters(prev => ({ ...prev, search }))} + /> +
+ +
+
+ +
+ + + + setViewMode(v as any)} className="hidden md:inline-flex"> + + + + + + + + + +
+
+ + {showFilters && ( + + + + + + )} +
+ + {filteredAndSortedParks.length > 0 ? ( + viewMode === 'grid' ? ( + + ) : ( + navigate(`/parks/${park.slug}`)} + /> + ) + ) : ( +
+ +

No parks found

+

+ {operator.name} doesn't operate any parks matching your criteria +

+
+ )} +
+
+ ); +} diff --git a/src/pages/OwnerParks.tsx b/src/pages/OwnerParks.tsx new file mode 100644 index 00000000..d287f223 --- /dev/null +++ b/src/pages/OwnerParks.tsx @@ -0,0 +1,278 @@ +import { useState, useEffect, useMemo } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Header } from '@/components/layout/Header'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Card, CardContent } from '@/components/ui/card'; +import { ArrowLeft, MapPin, Filter, FerrisWheel } from 'lucide-react'; +import { Park, Company } from '@/types/database'; +import { supabase } from '@/integrations/supabase/client'; +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 { ParkFilters } from '@/components/parks/ParkFilters'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Grid3X3, List } from 'lucide-react'; +import { FilterState, SortState } from './Parks'; + +const initialFilters: FilterState = { + search: '', + parkType: 'all', + status: 'all', + country: 'all', + minRating: 0, + maxRating: 5, + minRides: 0, + maxRides: 1000, + openingYearStart: null, + openingYearEnd: null, +}; + +const initialSort: SortState = { + field: 'name', + direction: 'asc' +}; + +export default function OwnerParks() { + const { ownerSlug } = useParams<{ ownerSlug: string }>(); + const navigate = useNavigate(); + const [owner, setOwner] = useState(null); + const [parks, setParks] = useState([]); + const [loading, setLoading] = useState(true); + const [filters, setFilters] = useState(initialFilters); + const [sort, setSort] = useState(initialSort); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); + const [showFilters, setShowFilters] = useState(false); + + useEffect(() => { + if (ownerSlug) { + fetchData(); + } + }, [ownerSlug]); + + const fetchData = async () => { + try { + // Fetch owner + const { data: ownerData, error: ownerError } = await supabase + .from('companies') + .select('*') + .eq('slug', ownerSlug) + .eq('company_type', 'property_owner') + .maybeSingle(); + + if (ownerError) throw ownerError; + setOwner(ownerData); + + if (ownerData) { + // Fetch parks owned by this property owner + const { data: parksData, error: parksError } = await supabase + .from('parks') + .select(` + *, + location:locations(*), + operator:companies!parks_operator_id_fkey(*), + property_owner:companies!parks_property_owner_id_fkey(*) + `) + .eq('property_owner_id', ownerData.id) + .order('name'); + + if (parksError) throw parksError; + setParks(parksData || []); + } + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + }; + + const filteredAndSortedParks = useMemo(() => { + let filtered = parks.filter(park => { + 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); + if (!matchesSearch) return false; + } + if (filters.parkType !== 'all' && park.park_type !== filters.parkType) return false; + if (filters.status !== 'all' && park.status !== filters.status) return false; + if (filters.country !== 'all' && park.location?.country !== filters.country) return false; + + const rating = park.average_rating || 0; + if (rating < filters.minRating || rating > filters.maxRating) return false; + + const rideCount = park.ride_count || 0; + if (rideCount < filters.minRides || rideCount > filters.maxRides) return false; + + return true; + }); + + 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; + default: + aValue = a.name; + bValue = b.name; + } + + if (typeof aValue === 'string' && typeof bValue === 'string') { + return sort.direction === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); + } + return sort.direction === 'asc' ? aValue - bValue : bValue - aValue; + }); + + return filtered; + }, [parks, filters, sort]); + + if (loading) { + return ( +
+
+
+
+
+
+ {[...Array(8)].map((_, i) => ( +
+ ))} +
+
+
+
+ ); + } + + if (!owner) { + return ( +
+
+
+
+

Property Owner Not Found

+ +
+
+
+ ); + } + + return ( +
+
+ +
+
+ +
+ +
+
+ +

Parks Owned by {owner.name}

+
+

+ Explore all theme parks owned by {owner.name} +

+ + + {filteredAndSortedParks.length} parks + +
+ +
+
+
+ setFilters(prev => ({ ...prev, search }))} + /> +
+ +
+
+ +
+ + + + setViewMode(v as any)} className="hidden md:inline-flex"> + + + + + + + + + +
+
+ + {showFilters && ( + + + + + + )} +
+ + {filteredAndSortedParks.length > 0 ? ( + viewMode === 'grid' ? ( + + ) : ( + navigate(`/parks/${park.slug}`)} + /> + ) + ) : ( +
+ +

No parks found

+

+ {owner.name} doesn't own any parks matching your criteria +

+
+ )} +
+
+ ); +} diff --git a/src/pages/PropertyOwnerDetail.tsx b/src/pages/PropertyOwnerDetail.tsx index e116d90c..81ddafc2 100644 --- a/src/pages/PropertyOwnerDetail.tsx +++ b/src/pages/PropertyOwnerDetail.tsx @@ -240,7 +240,18 @@ export default function PropertyOwnerDetail() { -

Parks owned by {owner.name} will be displayed here.

+
+

Parks

+ +
+

+ View all parks owned by {owner.name} +