diff --git a/src/components/profile/RideCreditFilters.tsx b/src/components/profile/RideCreditFilters.tsx index 4b0aa0e6..8f3a0966 100644 --- a/src/components/profile/RideCreditFilters.tsx +++ b/src/components/profile/RideCreditFilters.tsx @@ -7,7 +7,7 @@ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/component import { MultiSelectCombobox } from '@/components/ui/multi-select-combobox'; import { RideCreditFilters as FilterTypes } from '@/types/ride-credits'; import { UserRideCredit } from '@/types/database'; -import { Search, X, ChevronDown, MapPin, Building2, Factory, Calendar, Star, MessageSquare, Image } from 'lucide-react'; +import { X, ChevronDown, MapPin, Building2, Factory, Calendar, Star, MessageSquare, Image } from 'lucide-react'; import { useEffect, useMemo } from 'react'; import { Checkbox } from '@/components/ui/checkbox'; @@ -67,6 +67,48 @@ export function RideCreditFilters({ }; }, [credits]); + const searchOptions = useMemo(() => { + const rides = new Map(); + const parks = new Map(); + const manufacturers = new Map(); + + credits.forEach(credit => { + // Rides with their park context + if (credit.rides?.id && credit.rides?.name) { + rides.set(credit.rides.id, { + name: credit.rides.name, + park: credit.rides.parks?.name || 'Unknown Park' + }); + } + + // Parks + if (credit.rides?.parks?.id && credit.rides?.parks?.name) { + parks.set(credit.rides.parks.id, credit.rides.parks.name); + } + + // Manufacturers + if (credit.rides?.manufacturer?.id && credit.rides?.manufacturer?.name) { + manufacturers.set(credit.rides.manufacturer.id, credit.rides.manufacturer.name); + } + }); + + // Combine into single options array with type prefixes + return [ + ...Array.from(rides.entries()).map(([id, data]) => ({ + label: `🎢 ${data.name} (at ${data.park})`, + value: `ride:${id}`, + })), + ...Array.from(parks.entries()).map(([id, name]) => ({ + label: `🏰 ${name}`, + value: `park:${id}`, + })), + ...Array.from(manufacturers.entries()).map(([id, name]) => ({ + label: `🏭 ${name}`, + value: `manufacturer:${id}`, + })) + ].sort((a, b) => a.label.localeCompare(b.label)); + }, [credits]); + const maxRideCount = useMemo(() => { return Math.max(...credits.map(c => c.ride_count), 1); }, [credits]); @@ -107,26 +149,22 @@ export function RideCreditFilters({ return (
- {/* Search Bar - Always Visible */} -
- - onFilterChange('searchQuery', e.target.value || undefined)} - className="pl-10 pr-10" - /> - {filters.searchQuery && ( - - )} -
+ {/* Quick Search - Multi-select Combobox */} +
+ + + onFilterChange('selectedSearchItems', values.length > 0 ? values : undefined) + } + placeholder="Search for specific rides, parks, or manufacturers..." + searchPlaceholder="Type to search..." + emptyText="No matches found" + maxDisplay={3} + className={compact ? "h-9 text-sm" : ""} + /> +
{/* Quick Category Chips */}
@@ -375,12 +413,21 @@ export function RideCreditFilters({
- {filters.searchQuery && ( - - Search: {filters.searchQuery} - removeFilter('searchQuery')} /> - - )} + {filters.selectedSearchItems?.map(item => { + const option = searchOptions.find(opt => opt.value === item); + return option ? ( + + {option.label} + { + const updated = filters.selectedSearchItems!.filter(i => i !== item); + onFilterChange('selectedSearchItems', updated.length > 0 ? updated : undefined); + }} + /> + + ) : null; + })} {filters.categories?.map(cat => ( {categoryOptions.find(c => c.value === cat)?.icon} {cat} diff --git a/src/hooks/useRideCreditFilters.ts b/src/hooks/useRideCreditFilters.ts index 2ef67f26..9a7f93e3 100644 --- a/src/hooks/useRideCreditFilters.ts +++ b/src/hooks/useRideCreditFilters.ts @@ -43,7 +43,7 @@ export function useRideCreditFilters(credits: UserRideCredit[]) { const filteredCredits = useMemo(() => { let result = [...credits]; - // Search filter + // Search filter (text search) if (debouncedSearchQuery) { const search = debouncedSearchQuery.toLowerCase(); result = result.filter(credit => @@ -52,6 +52,21 @@ export function useRideCreditFilters(credits: UserRideCredit[]) { ); } + // Selected search items filter (multi-select combobox) + if (filters.selectedSearchItems && filters.selectedSearchItems.length > 0) { + result = result.filter(credit => { + return filters.selectedSearchItems!.some(item => { + const [type, id] = item.split(':'); + + if (type === 'ride' && credit.rides?.id === id) return true; + if (type === 'park' && credit.rides?.parks?.id === id) return true; + if (type === 'manufacturer' && credit.rides?.manufacturer?.id === id) return true; + + return false; + }); + }); + } + // Categories if (filters.categories && filters.categories.length > 0) { result = result.filter(credit => diff --git a/src/types/ride-credits.ts b/src/types/ride-credits.ts index 88cbf1a9..f03767fa 100644 --- a/src/types/ride-credits.ts +++ b/src/types/ride-credits.ts @@ -1,6 +1,7 @@ export interface RideCreditFilters { // Search searchQuery?: string; + selectedSearchItems?: string[]; // Format: "ride:id", "park:id", "manufacturer:id" // Category (Multi-select) categories?: string[];