Files
thrilltrack-explorer/src/components/lists/ListSearch.tsx
gpt-engineer-app[bot] 8ec0d59e75 Refactor user lists
2025-10-02 03:38:55 +00:00

178 lines
5.3 KiB
TypeScript

import { useState, useEffect } from "react";
import { supabase } from "@/integrations/supabase/client";
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) {
searchResults.push(
...rides.map((ride: any) => ({
id: ride.id,
name: ride.name,
type: "ride" as const,
subtitle: ride.park?.name || ride.category,
}))
);
}
}
// 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>
);
}