diff --git a/src/App.tsx b/src/App.tsx index 45348a62..5f3e77b1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -14,6 +14,7 @@ import RideDetail from "./pages/RideDetail"; import Rides from "./pages/Rides"; import Manufacturers from "./pages/Manufacturers"; import Designers from "./pages/Designers"; +import ParkOwners from "./pages/ParkOwners"; import Auth from "./pages/Auth"; import Profile from "./pages/Profile"; import UserSettings from "./pages/UserSettings"; @@ -44,6 +45,7 @@ function AppContent() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/park-owners/ParkOwnerCard.tsx b/src/components/park-owners/ParkOwnerCard.tsx new file mode 100644 index 00000000..cd4db822 --- /dev/null +++ b/src/components/park-owners/ParkOwnerCard.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Building2, Star, MapPin } from 'lucide-react'; +import { Company } from '@/types/database'; + +interface ParkOwnerCardProps { + company: Company; +} + +const ParkOwnerCard = ({ company }: ParkOwnerCardProps) => { + const navigate = useNavigate(); + + const handleClick = () => { + navigate(`/owners/${company.slug}/parks/`); + }; + + const getCompanyIcon = () => { + return ; + }; + + return ( + + +
+
+
+ {company.logo_url ? ( + {`${company.name} + ) : ( + getCompanyIcon() + )} +
+
+ + {company.name} + +
+ + Property Owner + + {company.founded_year && ( + + Est. {company.founded_year} + + )} +
+
+
+
+
+ + + {company.description && ( +

+ {company.description} +

+ )} + +
+ {company.headquarters_location && ( +
+ + {company.headquarters_location} +
+ )} + +
+ + + {company.average_rating > 0 + ? `${company.average_rating.toFixed(1)} rating` + : 'No ratings yet' + } + {company.review_count > 0 && ( + + ({company.review_count} review{company.review_count !== 1 ? 's' : ''}) + + )} + +
+
+ + +
+
+ ); +}; + +export default ParkOwnerCard; \ No newline at end of file diff --git a/src/pages/ParkOwners.tsx b/src/pages/ParkOwners.tsx new file mode 100644 index 00000000..90f598de --- /dev/null +++ b/src/pages/ParkOwners.tsx @@ -0,0 +1,175 @@ +import React, { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { Header } from '@/components/layout/Header'; +import { Input } from '@/components/ui/input'; +import { Button } from '@/components/ui/button'; +import { Badge } from '@/components/ui/badge'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Search, Filter, Building2 } from 'lucide-react'; +import { supabase } from '@/integrations/supabase/client'; +import ParkOwnerCard from '@/components/park-owners/ParkOwnerCard'; +import { Company } from '@/types/database'; + +const ParkOwners = () => { + const [searchTerm, setSearchTerm] = useState(''); + const [sortBy, setSortBy] = useState('name'); + const [filterBy, setFilterBy] = useState('all'); + + const { data: parkOwners, isLoading } = useQuery({ + queryKey: ['park-owners'], + queryFn: async () => { + // Get companies that are property owners + const { data, error } = await supabase + .from('companies') + .select('*') + .in('id', + await supabase + .from('parks') + .select('property_owner_id') + .not('property_owner_id', 'is', null) + .then(({ data }) => data?.map(park => park.property_owner_id) || []) + ) + .order('name'); + + if (error) throw error; + return data as Company[]; + }, + }); + + const filteredAndSortedOwners = React.useMemo(() => { + if (!parkOwners) return []; + + let filtered = parkOwners.filter(owner => { + const matchesSearch = owner.name.toLowerCase().includes(searchTerm.toLowerCase()) || + (owner.description && owner.description.toLowerCase().includes(searchTerm.toLowerCase())); + + if (filterBy === 'all') return matchesSearch; + if (filterBy === 'with-rating') return matchesSearch && owner.average_rating > 0; + if (filterBy === 'established') return matchesSearch && owner.founded_year; + + return matchesSearch; + }); + + // Sort + filtered.sort((a, b) => { + switch (sortBy) { + case 'name': + return a.name.localeCompare(b.name); + case 'rating': + return (b.average_rating || 0) - (a.average_rating || 0); + case 'founded': + return (b.founded_year || 0) - (a.founded_year || 0); + case 'reviews': + return (b.review_count || 0) - (a.review_count || 0); + default: + return 0; + } + }); + + return filtered; + }, [parkOwners, searchTerm, sortBy, filterBy]); + + return ( +
+
+ +
+
+
+ +
+
+

Property Owners

+

+ Discover companies that own and manage theme parks +

+
+
+ + {/* Search and Filters */} +
+
+ + setSearchTerm(e.target.value)} + className="pl-10" + /> +
+ + + + +
+ + {/* Results Count */} +
+
+ + {filteredAndSortedOwners?.length || 0} property owners + + {searchTerm && ( + + Searching: "{searchTerm}" + + )} +
+
+ + {/* Loading State */} + {isLoading && ( +
+
+

Loading property owners...

+
+ )} + + {/* Property Owners Grid */} + {!isLoading && filteredAndSortedOwners && ( +
+ {filteredAndSortedOwners.map((owner) => ( + + ))} +
+ )} + + {/* Empty State */} + {!isLoading && filteredAndSortedOwners?.length === 0 && ( +
+ +

No property owners found

+

+ {searchTerm + ? "Try adjusting your search terms or filters" + : "No property owners are currently available" + } +

+
+ )} +
+
+ ); +}; + +export default ParkOwners; \ No newline at end of file