mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 04:31:13 -05:00
Adds hover-preview UX by introducing preview cards for entities and wiring hoverable links: - Implements CompanyPreviewCard and ParkPreviewCard components plus hooks to fetch preview data - Adds HoverCard usage to ParkDetail and RideDetail for operator, manufacturer, and designer links - Creates preview wrappers for manufacturer/designer/operator links and updates related pages to use hover previews - Includes supporting updates to query keys and preview hooks to fetch minimal data for previews
113 lines
3.6 KiB
TypeScript
113 lines
3.6 KiB
TypeScript
import { MapPin, Star, FerrisWheel, Zap } from 'lucide-react';
|
|
import { useParkPreview } from '@/hooks/preview/useParkPreview';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Separator } from '@/components/ui/separator';
|
|
|
|
interface ParkPreviewCardProps {
|
|
slug: string;
|
|
}
|
|
|
|
export function ParkPreviewCard({ slug }: ParkPreviewCardProps) {
|
|
const { data: park, isLoading } = useParkPreview(slug);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="w-80">
|
|
<div className="animate-pulse space-y-3">
|
|
<div className="h-32 bg-muted rounded" />
|
|
<div className="h-4 bg-muted rounded w-3/4" />
|
|
<div className="h-4 bg-muted rounded w-1/2" />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!park) {
|
|
return (
|
|
<div className="w-80 p-4 text-center text-muted-foreground">
|
|
Park not found
|
|
</div>
|
|
);
|
|
}
|
|
|
|
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';
|
|
default:
|
|
return 'bg-red-500/20 text-red-400 border-red-500/30';
|
|
}
|
|
};
|
|
|
|
const formatParkType = (type: string) => {
|
|
return type.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
|
|
};
|
|
|
|
return (
|
|
<div className="w-80 space-y-3">
|
|
{/* Image */}
|
|
{park.card_image_url && (
|
|
<div className="aspect-video rounded-lg overflow-hidden bg-muted">
|
|
<img
|
|
src={park.card_image_url}
|
|
alt={park.name}
|
|
className="w-full h-full object-cover"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* Header */}
|
|
<div>
|
|
<h3 className="font-semibold text-base line-clamp-1 mb-2">{park.name}</h3>
|
|
<div className="flex items-center gap-2 flex-wrap">
|
|
<Badge className={`${getStatusColor(park.status)} border text-xs`}>
|
|
{park.status.replace('_', ' ').toUpperCase()}
|
|
</Badge>
|
|
<Badge variant="outline" className="text-xs">
|
|
{formatParkType(park.park_type)}
|
|
</Badge>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Location */}
|
|
{park.location && (
|
|
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
<MapPin className="w-4 h-4 flex-shrink-0" />
|
|
<span className="line-clamp-1">
|
|
{[park.location.city, park.location.state_province, park.location.country]
|
|
.filter(Boolean)
|
|
.join(', ')}
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
<Separator />
|
|
|
|
{/* Stats */}
|
|
<div className="grid grid-cols-2 gap-3 text-sm">
|
|
<div className="flex items-center gap-2">
|
|
<FerrisWheel className="w-4 h-4 text-primary" />
|
|
<span className="font-medium">{park.ride_count || 0}</span>
|
|
<span className="text-muted-foreground">rides</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Zap className="w-4 h-4 text-accent" />
|
|
<span className="font-medium">{park.coaster_count || 0}</span>
|
|
<span className="text-muted-foreground">coasters</span>
|
|
</div>
|
|
{park.average_rating && park.average_rating > 0 && (
|
|
<div className="flex items-center gap-2 col-span-2">
|
|
<Star className="w-4 h-4 text-yellow-500 fill-yellow-500" />
|
|
<span className="font-medium">{park.average_rating.toFixed(1)}</span>
|
|
<span className="text-muted-foreground">({park.review_count} reviews)</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|