From 48bf6c782397f55461b48836ad7d5d3e2f1a4453 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 28 Oct 2025 16:08:32 +0000 Subject: [PATCH] Refactor: Enhance Park List View --- src/components/designers/DesignerListView.tsx | 115 ++++++++++++++ .../manufacturers/ManufacturerListView.tsx | 115 ++++++++++++++ src/components/operators/OperatorListView.tsx | 117 ++++++++++++++ src/components/parks/ParkListView.tsx | 115 ++++++++------ src/components/rides/RideListView.tsx | 149 ++++++++++++++++++ src/index.css | 17 ++ src/pages/Designers.tsx | 25 ++- src/pages/Manufacturers.tsx | 25 ++- src/pages/Operators.tsx | 27 +++- src/pages/Rides.tsx | 26 ++- 10 files changed, 659 insertions(+), 72 deletions(-) create mode 100644 src/components/designers/DesignerListView.tsx create mode 100644 src/components/manufacturers/ManufacturerListView.tsx create mode 100644 src/components/operators/OperatorListView.tsx create mode 100644 src/components/rides/RideListView.tsx diff --git a/src/components/designers/DesignerListView.tsx b/src/components/designers/DesignerListView.tsx new file mode 100644 index 00000000..8de2374b --- /dev/null +++ b/src/components/designers/DesignerListView.tsx @@ -0,0 +1,115 @@ +import { MapPin, Star, Ruler, Calendar, Palette } from 'lucide-react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Company } from '@/types/database'; +import { cn } from '@/lib/utils'; + +interface DesignerListViewProps { + designers: Company[]; + onDesignerClick: (designer: Company) => void; +} + +export function DesignerListView({ designers, onDesignerClick }: DesignerListViewProps) { + return ( +
+ {designers.map((designer, index) => ( + onDesignerClick(designer)} + > + +
+ {/* Logo */} +
+ {designer.logo_url ? ( + {designer.name} + ) : ( +
+ +
+ )} +
+ + {/* Content */} +
+
+ {/* Header */} +
+
+

+ {designer.name} +

+ + {designer.headquarters_location && ( +
+ + {designer.headquarters_location} +
+ )} +
+ + {/* Rating */} + {designer.average_rating && designer.average_rating > 0 && ( +
+ + {designer.average_rating.toFixed(1)} + {designer.review_count && designer.review_count > 0 && ( + ({designer.review_count}) + )} +
+ )} +
+ + {/* Description */} + {designer.description && ( +

+ {designer.description} +

+ )} + + {/* Tags */} +
+ + + Designer + + {designer.founded_year && ( + + + Est. {designer.founded_year} + + )} +
+
+ + {/* Actions */} +
+ +
+
+
+
+
+ ))} +
+ ); +} diff --git a/src/components/manufacturers/ManufacturerListView.tsx b/src/components/manufacturers/ManufacturerListView.tsx new file mode 100644 index 00000000..afd47187 --- /dev/null +++ b/src/components/manufacturers/ManufacturerListView.tsx @@ -0,0 +1,115 @@ +import { MapPin, Star, Factory, Calendar, Wrench } from 'lucide-react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Company } from '@/types/database'; +import { cn } from '@/lib/utils'; + +interface ManufacturerListViewProps { + manufacturers: Company[]; + onManufacturerClick: (manufacturer: Company) => void; +} + +export function ManufacturerListView({ manufacturers, onManufacturerClick }: ManufacturerListViewProps) { + return ( +
+ {manufacturers.map((manufacturer, index) => ( + onManufacturerClick(manufacturer)} + > + +
+ {/* Logo */} +
+ {manufacturer.logo_url ? ( + {manufacturer.name} + ) : ( +
+ +
+ )} +
+ + {/* Content */} +
+
+ {/* Header */} +
+
+

+ {manufacturer.name} +

+ + {manufacturer.headquarters_location && ( +
+ + {manufacturer.headquarters_location} +
+ )} +
+ + {/* Rating */} + {manufacturer.average_rating && manufacturer.average_rating > 0 && ( +
+ + {manufacturer.average_rating.toFixed(1)} + {manufacturer.review_count && manufacturer.review_count > 0 && ( + ({manufacturer.review_count}) + )} +
+ )} +
+ + {/* Description */} + {manufacturer.description && ( +

+ {manufacturer.description} +

+ )} + + {/* Tags */} +
+ + + Manufacturer + + {manufacturer.founded_year && ( + + + Est. {manufacturer.founded_year} + + )} +
+
+ + {/* Actions */} +
+ +
+
+
+
+
+ ))} +
+ ); +} diff --git a/src/components/operators/OperatorListView.tsx b/src/components/operators/OperatorListView.tsx new file mode 100644 index 00000000..03259cce --- /dev/null +++ b/src/components/operators/OperatorListView.tsx @@ -0,0 +1,117 @@ +import { MapPin, Star, Building2, Calendar, Globe } from 'lucide-react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Company } from '@/types/database'; +import { cn } from '@/lib/utils'; + +interface OperatorListViewProps { + operators: (Company & { park_count?: number })[]; + onOperatorClick: (operator: Company) => void; +} + +export function OperatorListView({ operators, onOperatorClick }: OperatorListViewProps) { + return ( +
+ {operators.map((operator, index) => ( + onOperatorClick(operator)} + > + +
+ {/* Logo */} +
+ {operator.logo_url ? ( + {operator.name} + ) : ( +
+ +
+ )} +
+ + {/* Content */} +
+
+ {/* Header */} +
+
+

+ {operator.name} +

+ + {operator.headquarters_location && ( +
+ + {operator.headquarters_location} +
+ )} +
+ + {/* Rating */} + {operator.average_rating && operator.average_rating > 0 && ( +
+ + {operator.average_rating.toFixed(1)} + {operator.review_count && operator.review_count > 0 && ( + ({operator.review_count}) + )} +
+ )} +
+ + {/* Description */} + {operator.description && ( +

+ {operator.description} +

+ )} + + {/* Tags */} +
+ {operator.founded_year && ( + + + Founded {operator.founded_year} + + )} + {operator.park_count !== undefined && operator.park_count > 0 && ( + + + {operator.park_count} {operator.park_count === 1 ? 'park' : 'parks'} + + )} +
+
+ + {/* Actions */} +
+ +
+
+
+
+
+ ))} +
+ ); +} diff --git a/src/components/parks/ParkListView.tsx b/src/components/parks/ParkListView.tsx index 4ddb986f..a98b5697 100644 --- a/src/components/parks/ParkListView.tsx +++ b/src/components/parks/ParkListView.tsx @@ -1,8 +1,9 @@ -import { MapPin, Star, Users, Calendar, ExternalLink, Castle, FerrisWheel, Waves, Tent } from 'lucide-react'; +import { MapPin, Star, Users, Calendar, ExternalLink, Castle, FerrisWheel, Waves, Tent, Sparkles } from 'lucide-react'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Park } from '@/types/database'; +import { cn } from '@/lib/utils'; interface ParkListViewProps { parks: Park[]; @@ -36,86 +37,103 @@ export function ParkListView({ parks, onParkClick }: ParkListViewProps) { }; return ( -
- {parks.map((park) => ( +
+ {parks.map((park, index) => ( onParkClick(park)} > -
+
{/* Image */} -
+
{park.card_image_url ? ( - {park.name} + <> + {park.name} +
+ ) : ( -
- +
+ {getParkTypeIcon(park.park_type)} +
)} {/* Status Badge */} {park.status.replace('_', ' ').toUpperCase()}
{/* Content */} -
-
+
+
{/* Header */} -
-
-
-

+
+
+
+

{park.name}

- {getParkTypeIcon(park.park_type)} + + {getParkTypeIcon(park.park_type)} +
{park.location && ( -
- - {park.location.city && `${park.location.city}, `} - {park.location.state_province && `${park.location.state_province}, `} - {park.location.country} +
+ + + {park.location.city && `${park.location.city}, `} + {park.location.state_province && `${park.location.state_province}, `} + {park.location.country} +
)}
{/* Rating */} {park.average_rating > 0 && ( -
+
- {park.average_rating.toFixed(1)} - ({park.review_count}) + {park.average_rating.toFixed(1)} + ({park.review_count})
)}
{/* Description */} {park.description && ( -

+

{park.description}

)} {/* Tags */} -
- +
+ {formatParkType(park.park_type)} {park.opening_date && ( - + Opened {park.opening_date.split('-')[0]} @@ -124,30 +142,33 @@ export function ParkListView({ parks, onParkClick }: ParkListViewProps) {
{/* Stats and Actions */} -
-
-
- {park.ride_count || 0} - rides +
+
+
+ + {park.ride_count || 0} + rides
-
- {park.coaster_count || 0} - +
+ + {park.coaster_count || 0} + coasters
{park.review_count > 0 && ( -
- - {park.review_count} reviews +
+ + {park.review_count} reviews
)}
diff --git a/src/components/rides/RideListView.tsx b/src/components/rides/RideListView.tsx new file mode 100644 index 00000000..8ffdb459 --- /dev/null +++ b/src/components/rides/RideListView.tsx @@ -0,0 +1,149 @@ +import { MapPin, Star, Zap, Ruler, Clock, Factory, Castle } from 'lucide-react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Ride } from '@/types/database'; +import { cn } from '@/lib/utils'; + +interface RideListViewProps { + rides: Ride[]; + onRideClick: (ride: Ride) => void; +} + +export function RideListView({ rides, onRideClick }: RideListViewProps) { + const getStatusColor = (status: string) => { + switch (status) { + case 'operating': return 'bg-green-500/20 text-green-400 border-green-500/30'; + case 'seasonal': return 'bg-yellow-500/20 text-yellow-400 border-yellow-500/30'; + case 'under_construction': return 'bg-blue-500/20 text-blue-400 border-blue-500/30'; + case 'closed': return 'bg-red-500/20 text-red-400 border-red-500/30'; + default: return 'bg-gray-500/20 text-gray-400 border-gray-500/30'; + } + }; + + const formatCategory = (category: string) => { + return category.split('_').map(word => + word.charAt(0).toUpperCase() + word.slice(1) + ).join(' '); + }; + + return ( +
+ {rides.map((ride, index) => ( + onRideClick(ride)} + > + +
+ {/* Image */} +
+ {ride.card_image_url ? ( + <> + {ride.name} +
+ + ) : ( +
+ +
+
+ )} + + {/* Status Badge */} + + {ride.status.replace('_', ' ').toUpperCase()} + +
+ + {/* Content */} +
+
+ {/* Header */} +
+
+

+ {ride.name} +

+ + {ride.park && ( +
+ + {ride.park.name} +
+ )} + + {ride.park?.location && ( +
+ + + {ride.park.location.city && `${ride.park.location.city}, `} + {ride.park.location.country} + +
+ )} +
+ + {/* Rating */} + {ride.average_rating > 0 && ( +
+ + {ride.average_rating.toFixed(1)} +
+ )} +
+ + {/* Tags */} +
+ + {formatCategory(ride.category)} + + {ride.manufacturer && ( + + + {ride.manufacturer.name} + + )} +
+
+ + {/* Actions */} +
+
+ {ride.review_count && ride.review_count > 0 && ( + {ride.review_count} reviews + )} +
+ + +
+
+
+ + + ))} +
+ ); +} diff --git a/src/index.css b/src/index.css index b36b18a8..7ad01b60 100644 --- a/src/index.css +++ b/src/index.css @@ -392,3 +392,20 @@ All colors MUST be HSL. content-visibility: auto; contain-intrinsic-size: 0 200px; } + +/* Enhanced list view animations */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-fade-in-up { + animation: fadeInUp 0.6s ease-out forwards; + opacity: 0; +} diff --git a/src/pages/Designers.tsx b/src/pages/Designers.tsx index 2148c92b..b04e83fc 100644 --- a/src/pages/Designers.tsx +++ b/src/pages/Designers.tsx @@ -8,12 +8,14 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { Search, SlidersHorizontal, Ruler, Plus, ChevronDown, Filter, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; +import { Search, SlidersHorizontal, Ruler, Plus, ChevronDown, Filter, PanelLeftClose, PanelLeftOpen, Grid3X3, List } from 'lucide-react'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { cn } from '@/lib/utils'; import { DesignerFilters, DesignerFilterState, defaultDesignerFilters } from '@/components/designers/DesignerFilters'; import { Company } from '@/types/database'; import { supabase } from '@/integrations/supabase/client'; import { DesignerCard } from '@/components/designers/DesignerCard'; +import { DesignerListView } from '@/components/designers/DesignerListView'; import { DesignerForm } from '@/components/admin/DesignerForm'; import { useAuth } from '@/hooks/useAuth'; import { useUserRole } from '@/hooks/useUserRole'; @@ -33,6 +35,7 @@ export default function Designers() { const [sortBy, setSortBy] = useState('name'); const [filters, setFilters] = useState(defaultDesignerFilters); const [showFilters, setShowFilters] = useState(false); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { const saved = localStorage.getItem('designers-sidebar-collapsed'); @@ -200,6 +203,12 @@ export default function Designers() { Filters + setViewMode(v as 'grid' | 'list')} className="hidden md:inline-flex"> + + + + +
@@ -246,11 +255,15 @@ export default function Designers() { {/* Results Area */}
{filteredCompanies.length > 0 ? ( -
- {filteredCompanies.map((company) => ( - - ))} -
+ viewMode === 'grid' ? ( +
+ {filteredCompanies.map((company) => ( + + ))} +
+ ) : ( + navigate(`/designers/${d.slug}`)} /> + ) ) : (
diff --git a/src/pages/Manufacturers.tsx b/src/pages/Manufacturers.tsx index c07d53fd..4455d5dd 100644 --- a/src/pages/Manufacturers.tsx +++ b/src/pages/Manufacturers.tsx @@ -8,12 +8,14 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { Search, SlidersHorizontal, Factory, Plus, ChevronDown, Filter, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; +import { Search, SlidersHorizontal, Factory, Plus, ChevronDown, Filter, PanelLeftClose, PanelLeftOpen, Grid3X3, List } from 'lucide-react'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { cn } from '@/lib/utils'; import { ManufacturerFilters, ManufacturerFilterState, defaultManufacturerFilters } from '@/components/manufacturers/ManufacturerFilters'; import { Company } from '@/types/database'; import { supabase } from '@/integrations/supabase/client'; import { ManufacturerCard } from '@/components/manufacturers/ManufacturerCard'; +import { ManufacturerListView } from '@/components/manufacturers/ManufacturerListView'; import { ManufacturerForm } from '@/components/admin/ManufacturerForm'; import { useAuth } from '@/hooks/useAuth'; import { useUserRole } from '@/hooks/useUserRole'; @@ -33,6 +35,7 @@ export default function Manufacturers() { const [sortBy, setSortBy] = useState('name'); const [filters, setFilters] = useState(defaultManufacturerFilters); const [showFilters, setShowFilters] = useState(false); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { const saved = localStorage.getItem('manufacturers-sidebar-collapsed'); @@ -213,6 +216,12 @@ export default function Manufacturers() { Filters + setViewMode(v as 'grid' | 'list')} className="hidden md:inline-flex"> + + + + +
@@ -259,11 +268,15 @@ export default function Manufacturers() { {/* Results Area */}
{filteredCompanies.length > 0 ? ( -
- {filteredCompanies.map((company) => ( - - ))} -
+ viewMode === 'grid' ? ( +
+ {filteredCompanies.map((company) => ( + + ))} +
+ ) : ( + navigate(`/manufacturers/${m.slug}`)} /> + ) ) : (
diff --git a/src/pages/Operators.tsx b/src/pages/Operators.tsx index 3f2b02b8..b930cdeb 100644 --- a/src/pages/Operators.tsx +++ b/src/pages/Operators.tsx @@ -9,10 +9,12 @@ import { Dialog, DialogContent } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Collapsible, CollapsibleContent } from '@/components/ui/collapsible'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Search, Filter, Building, Plus, ChevronDown, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; +import { Search, Filter, Building, Plus, ChevronDown, PanelLeftClose, PanelLeftOpen, Grid3X3, List } from 'lucide-react'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { cn } from '@/lib/utils'; import { supabase } from '@/integrations/supabase/client'; import OperatorCard from '@/components/operators/OperatorCard'; +import { OperatorListView } from '@/components/operators/OperatorListView'; import { OperatorForm } from '@/components/admin/OperatorForm'; import { OperatorFilters, OperatorFilterState, defaultOperatorFilters } from '@/components/operators/OperatorFilters'; import { Company } from '@/types/database'; @@ -32,6 +34,7 @@ const Operators = () => { const [sortBy, setSortBy] = useState('name'); const [filters, setFilters] = useState(defaultOperatorFilters); const [showFilters, setShowFilters] = useState(false); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { const saved = localStorage.getItem('operators-sidebar-collapsed'); @@ -271,6 +274,12 @@ const Operators = () => { Filters + setViewMode(v as 'grid' | 'list')} className="hidden md:inline-flex"> + + + + +
@@ -324,13 +333,17 @@ const Operators = () => {
)} - {/* Operators Grid */} + {/* Operators Grid/List */} {!isLoading && filteredAndSortedOperators && filteredAndSortedOperators.length > 0 && ( -
- {filteredAndSortedOperators.map((operator) => ( - - ))} -
+ viewMode === 'grid' ? ( +
+ {filteredAndSortedOperators.map((operator) => ( + + ))} +
+ ) : ( + navigate(`/operators/${op.slug}`)} /> + ) )} {/* Empty State */} diff --git a/src/pages/Rides.tsx b/src/pages/Rides.tsx index 3b94f5fa..09ae9857 100644 --- a/src/pages/Rides.tsx +++ b/src/pages/Rides.tsx @@ -7,10 +7,12 @@ import { Dialog, DialogContent } from '@/components/ui/dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { Filter, SlidersHorizontal, FerrisWheel, Plus, ChevronDown, PanelLeftClose, PanelLeftOpen } from 'lucide-react'; +import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { Filter, SlidersHorizontal, FerrisWheel, Plus, ChevronDown, PanelLeftClose, PanelLeftOpen, Grid3X3, List } from 'lucide-react'; import { cn } from '@/lib/utils'; import { AutocompleteSearch } from '@/components/search/AutocompleteSearch'; import { RideCard } from '@/components/rides/RideCard'; +import { RideListView } from '@/components/rides/RideListView'; import { RideForm } from '@/components/admin/RideForm'; import { RideFilters, RideFilterState, defaultRideFilters } from '@/components/rides/RideFilters'; import { Ride } from '@/types/database'; @@ -32,6 +34,7 @@ export default function Rides() { const [sortBy, setSortBy] = useState('name'); const [filters, setFilters] = useState(defaultRideFilters); const [showFilters, setShowFilters] = useState(false); + const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [sidebarCollapsed, setSidebarCollapsed] = useState(() => { const saved = localStorage.getItem('rides-sidebar-collapsed'); @@ -221,6 +224,13 @@ export default function Rides() { Filters + + setViewMode(v as 'grid' | 'list')} className="hidden md:inline-flex"> + + + + +
@@ -267,11 +277,15 @@ export default function Rides() { {/* Results Area */}
{filteredRides.length > 0 ? ( -
- {filteredRides.map((ride) => ( - - ))} -
+ viewMode === 'grid' ? ( +
+ {filteredRides.map((ride) => ( + + ))} +
+ ) : ( + navigate(`/parks/${ride.park?.slug}/rides/${ride.slug}`)} /> + ) ) : (