Files
thrilltrack-explorer/src/components/search/EnhancedSearchResults.tsx
gpt-engineer-app[bot] 3c7de96bcf Fix date display issues
2025-10-02 16:34:38 +00:00

242 lines
8.4 KiB
TypeScript

import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Card, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Star, MapPin, Zap, Factory, Clock, Users, Calendar, Ruler, Gauge, Building } from 'lucide-react';
import { SearchResult } from '@/hooks/useSearch';
interface EnhancedSearchResultsProps {
results: SearchResult[];
loading: boolean;
hasMore?: boolean;
onLoadMore?: () => void;
}
export function EnhancedSearchResults({ results, loading, hasMore, onLoadMore }: EnhancedSearchResultsProps) {
const navigate = useNavigate();
const handleResultClick = (result: SearchResult) => {
switch (result.type) {
case 'park':
navigate(`/parks/${result.slug || result.id}`);
break;
case 'ride':
// Need to get park slug for ride navigation
navigate(`/rides/${result.id}`);
break;
case 'company':
navigate(`/companies/${result.id}`);
break;
}
};
const getTypeIcon = (type: string, size = 'w-6 h-6') => {
switch (type) {
case 'park': return <MapPin className={size} />;
case 'ride': return <Zap className={size} />;
case 'company': return <Factory className={size} />;
default: return <MapPin className={size} />;
}
};
const getTypeColor = (type: string) => {
switch (type) {
case 'park':
return 'bg-primary/10 text-primary border-primary/20';
case 'ride':
return 'bg-secondary/10 text-secondary border-secondary/20';
case 'company':
return 'bg-accent/10 text-accent border-accent/20';
default:
return 'bg-muted text-muted-foreground';
}
};
const renderParkDetails = (result: SearchResult) => {
if (result.type !== 'park') return null;
const parkData = result.data as any; // Type assertion for park-specific properties
return (
<div className="flex flex-wrap gap-2 mt-3">
{parkData?.ride_count && (
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Zap className="w-3 h-3" />
<span>{parkData.ride_count} rides</span>
</div>
)}
{parkData?.opening_date && (
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Calendar className="w-3 h-3" />
<span>Opened {parkData.opening_date.split('-')[0]}</span>
</div>
)}
{parkData?.status && (
<Badge variant="outline" className="text-xs">
{parkData.status.replace('_', ' ')}
</Badge>
)}
</div>
);
};
const renderRideDetails = (result: SearchResult) => {
if (result.type !== 'ride') return null;
const rideData = result.data as any; // Type assertion for ride-specific properties
return (
<div className="flex flex-wrap gap-2 mt-3">
{rideData?.category && (
<Badge variant="outline" className="text-xs">
{rideData.category.replace('_', ' ')}
</Badge>
)}
{rideData?.max_height_meters && (
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Ruler className="w-3 h-3" />
<span>{rideData.max_height_meters}m</span>
</div>
)}
{rideData?.max_speed_kmh && (
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Gauge className="w-3 h-3" />
<span>{rideData.max_speed_kmh} km/h</span>
</div>
)}
{rideData?.intensity_level && (
<Badge variant="outline" className="text-xs">
{rideData.intensity_level}
</Badge>
)}
</div>
);
};
const renderCompanyDetails = (result: SearchResult) => {
if (result.type !== 'company') return null;
const companyData = result.data as any; // Type assertion for company-specific properties
return (
<div className="flex flex-wrap gap-2 mt-3">
{companyData?.company_type && (
<Badge variant="outline" className="text-xs">
{companyData.company_type.replace('_', ' ')}
</Badge>
)}
{companyData?.founded_year && (
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Calendar className="w-3 h-3" />
<span>Founded {companyData.founded_year}</span>
</div>
)}
{companyData?.headquarters_location && (
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Building className="w-3 h-3" />
<span>{companyData.headquarters_location}</span>
</div>
)}
</div>
);
};
if (loading && results.length === 0) {
return (
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
);
}
if (results.length === 0) {
return null;
}
return (
<div className="space-y-4">
<div className="grid gap-4">
{results.map((result) => (
<Card
key={result.id}
onClick={() => handleResultClick(result)}
className="group cursor-pointer hover:shadow-lg hover:shadow-primary/10 transition-all duration-300 border-border/50 hover:border-primary/50"
>
<CardContent className="p-6">
<div className="flex gap-4">
{/* Image placeholder or actual image */}
<div className="flex-shrink-0">
{result.image ? (
<img
src={result.image}
alt={result.title}
className="w-16 h-16 object-cover rounded-lg"
/>
) : (
<div className="w-16 h-16 bg-muted rounded-lg flex items-center justify-center">
{getTypeIcon(result.type, 'w-8 h-8')}
</div>
)}
</div>
{/* Content */}
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2">
<h3 className="font-semibold text-lg group-hover:text-primary transition-colors truncate">
{result.title}
</h3>
<Badge variant="outline" className={`text-xs ${getTypeColor(result.type)}`}>
{result.type}
</Badge>
</div>
<p className="text-muted-foreground text-sm mb-2">
{result.subtitle}
</p>
{/* Type-specific details */}
{result.type === 'park' && renderParkDetails(result)}
{result.type === 'ride' && renderRideDetails(result)}
{result.type === 'company' && renderCompanyDetails(result)}
</div>
{/* Rating */}
{result.rating && result.rating > 0 && (
<div className="flex items-center gap-1 flex-shrink-0">
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
<span className="text-sm font-medium">{result.rating.toFixed(1)}</span>
{result.data?.review_count && (
<span className="text-xs text-muted-foreground">
({result.data.review_count})
</span>
)}
</div>
)}
</div>
</div>
</div>
</CardContent>
</Card>
))}
</div>
{/* Load More Button */}
{hasMore && (
<div className="flex justify-center py-4">
<Button
onClick={onLoadMore}
variant="outline"
disabled={loading}
className="min-w-32"
>
{loading ? (
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-primary"></div>
) : (
'Load More'
)}
</Button>
</div>
)}
</div>
);
}