mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 16:51:12 -05:00
185 lines
5.5 KiB
TypeScript
185 lines
5.5 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { supabase } from "@/lib/supabaseClient";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Search, Plus, X } from "lucide-react";
|
|
import { useDebounce } from "@/hooks/useDebounce";
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
|
interface ListSearchProps {
|
|
listType: string;
|
|
onSelect: (entityType: string, entityId: string, entityName: string) => void;
|
|
onClose: () => void;
|
|
}
|
|
|
|
interface SearchResult {
|
|
id: string;
|
|
name: string;
|
|
type: "park" | "ride" | "company";
|
|
subtitle?: string;
|
|
}
|
|
|
|
export function ListSearch({ listType, onSelect, onClose }: ListSearchProps) {
|
|
const [query, setQuery] = useState("");
|
|
const [results, setResults] = useState<SearchResult[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const debouncedQuery = useDebounce(query, 300);
|
|
|
|
useEffect(() => {
|
|
if (debouncedQuery.length >= 2) {
|
|
searchEntities();
|
|
} else {
|
|
setResults([]);
|
|
}
|
|
}, [debouncedQuery, listType]);
|
|
|
|
const searchEntities = async () => {
|
|
setLoading(true);
|
|
const searchResults: SearchResult[] = [];
|
|
|
|
// Determine which entity types to search based on list type
|
|
const shouldSearchParks = listType === "parks" || listType === "mixed";
|
|
const shouldSearchRides = listType === "rides" || listType === "coasters" || listType === "mixed";
|
|
const shouldSearchCompanies = listType === "companies" || listType === "mixed";
|
|
|
|
// Search parks
|
|
if (shouldSearchParks) {
|
|
const { data: parks } = await supabase
|
|
.from("parks")
|
|
.select("id, name, park_type, location_id")
|
|
.ilike("name", `%${debouncedQuery}%`)
|
|
.limit(10);
|
|
|
|
if (parks) {
|
|
searchResults.push(
|
|
...parks.map((park) => ({
|
|
id: park.id,
|
|
name: park.name,
|
|
type: "park" as const,
|
|
subtitle: park.park_type,
|
|
}))
|
|
);
|
|
}
|
|
}
|
|
|
|
// Search rides
|
|
if (shouldSearchRides) {
|
|
const { data: rides } = await supabase
|
|
.from("rides")
|
|
.select("id, name, category, park:parks(name)")
|
|
.ilike("name", `%${debouncedQuery}%`)
|
|
.limit(10);
|
|
|
|
if (rides) {
|
|
interface RideSearchResult {
|
|
id: string;
|
|
name: string;
|
|
park?: { name: string } | null;
|
|
category?: string | null;
|
|
}
|
|
|
|
searchResults.push(
|
|
...rides.map((ride: RideSearchResult) => ({
|
|
id: ride.id,
|
|
name: ride.name,
|
|
type: "ride" as const,
|
|
subtitle: ride.park?.name || ride.category || 'Unknown',
|
|
}))
|
|
);
|
|
}
|
|
}
|
|
|
|
// Search companies
|
|
if (shouldSearchCompanies) {
|
|
const { data: companies } = await supabase
|
|
.from("companies")
|
|
.select("id, name, company_type")
|
|
.ilike("name", `%${debouncedQuery}%`)
|
|
.limit(10);
|
|
|
|
if (companies) {
|
|
searchResults.push(
|
|
...companies.map((company) => ({
|
|
id: company.id,
|
|
name: company.name,
|
|
type: "company" as const,
|
|
subtitle: company.company_type,
|
|
}))
|
|
);
|
|
}
|
|
}
|
|
|
|
setResults(searchResults);
|
|
setLoading(false);
|
|
};
|
|
|
|
return (
|
|
<div className="border rounded-lg p-4 bg-card space-y-3">
|
|
<div className="flex items-center gap-2">
|
|
<div className="relative flex-1">
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
<Input
|
|
value={query}
|
|
onChange={(e) => setQuery(e.target.value)}
|
|
placeholder="Search for parks, rides, or companies..."
|
|
className="pl-9"
|
|
/>
|
|
</div>
|
|
<Button variant="ghost" size="icon" onClick={onClose}>
|
|
<X className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
|
|
{loading && (
|
|
<div className="text-center py-4 text-muted-foreground">
|
|
Searching...
|
|
</div>
|
|
)}
|
|
|
|
{!loading && results.length === 0 && debouncedQuery.length >= 2 && (
|
|
<div className="text-center py-4 text-muted-foreground">
|
|
No results found. Try a different search term.
|
|
</div>
|
|
)}
|
|
|
|
{!loading && results.length > 0 && (
|
|
<div className="space-y-2 max-h-64 overflow-y-auto">
|
|
{results.map((result) => (
|
|
<div
|
|
key={`${result.type}-${result.id}`}
|
|
className="flex items-center justify-between p-2 rounded hover:bg-muted transition-colors"
|
|
>
|
|
<div className="flex-1">
|
|
<p className="font-medium">{result.name}</p>
|
|
<div className="flex gap-2 mt-1">
|
|
<Badge variant="secondary" className="text-xs">
|
|
{result.type}
|
|
</Badge>
|
|
{result.subtitle && (
|
|
<Badge variant="outline" className="text-xs">
|
|
{result.subtitle}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<Button
|
|
size="sm"
|
|
onClick={() => onSelect(result.type, result.id, result.name)}
|
|
>
|
|
<Plus className="h-4 w-4 mr-1" />
|
|
Add
|
|
</Button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{debouncedQuery.length < 2 && (
|
|
<div className="text-center py-4 text-muted-foreground text-sm">
|
|
Type at least 2 characters to search
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|