diff --git a/src/components/search/SearchFilters.tsx b/src/components/search/SearchFilters.tsx index 2f28d0fe..91328f05 100644 --- a/src/components/search/SearchFilters.tsx +++ b/src/components/search/SearchFilters.tsx @@ -8,6 +8,8 @@ import { Slider } from '@/components/ui/slider'; import { Badge } from '@/components/ui/badge'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; import { ChevronDown, Filter, X } from 'lucide-react'; +import { Combobox } from '@/components/ui/combobox'; +import { useCountries, useStatesProvinces, useManufacturers, useCompanyHeadquarters } from '@/hooks/useAutocompleteData'; export interface SearchFilters { // Park filters @@ -47,7 +49,13 @@ interface SearchFiltersProps { export function SearchFiltersComponent({ filters, onFiltersChange, activeTab }: SearchFiltersProps) { const [isOpen, setIsOpen] = useState(false); - + + // Fetch autocomplete data + const { countries, loading: countriesLoading } = useCountries(); + const { statesProvinces, loading: statesLoading } = useStatesProvinces(filters.country); + const { manufacturers, loading: manufacturersLoading } = useManufacturers(); + const { headquarters, loading: headquartersLoading } = useCompanyHeadquarters(); + const updateFilter = (key: keyof SearchFilters, value: any) => { onFiltersChange({ ...filters, [key]: value }); }; @@ -145,19 +153,26 @@ export function SearchFiltersComponent({ filters, onFiltersChange, activeTab }:
- updateFilter('country', e.target.value || undefined)} + updateFilter('country', value || undefined)} + placeholder="Select country" + searchPlaceholder="Search countries..." + loading={countriesLoading} />
- updateFilter('stateProvince', e.target.value || undefined)} + updateFilter('stateProvince', value || undefined)} + placeholder="Select state/province" + searchPlaceholder="Search states/provinces..." + loading={statesLoading} + disabled={!filters.country} />
@@ -273,6 +288,18 @@ export function SearchFiltersComponent({ filters, onFiltersChange, activeTab }: +
+ + updateFilter('manufacturer', value || undefined)} + placeholder="Select manufacturer" + searchPlaceholder="Search manufacturers..." + loading={manufacturersLoading} + /> +
+
@@ -343,10 +370,13 @@ export function SearchFiltersComponent({ filters, onFiltersChange, activeTab }:
- updateFilter('headquarters', e.target.value || undefined)} + updateFilter('headquarters', value || undefined)} + placeholder="Select headquarters" + searchPlaceholder="Search headquarters..." + loading={headquartersLoading} />
diff --git a/src/components/ui/combobox.tsx b/src/components/ui/combobox.tsx new file mode 100644 index 00000000..9a80a32d --- /dev/null +++ b/src/components/ui/combobox.tsx @@ -0,0 +1,96 @@ +import * as React from "react"; +import { Check, ChevronsUpDown } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + +export interface ComboboxOption { + label: string; + value: string; +} + +interface ComboboxProps { + options: ComboboxOption[]; + value?: string; + onValueChange?: (value: string) => void; + placeholder?: string; + searchPlaceholder?: string; + emptyText?: string; + disabled?: boolean; + className?: string; + loading?: boolean; +} + +export function Combobox({ + options, + value, + onValueChange, + placeholder = "Select option...", + searchPlaceholder = "Search...", + emptyText = "No options found.", + disabled = false, + className, + loading = false, +}: ComboboxProps) { + const [open, setOpen] = React.useState(false); + + const selectedOption = options.find((option) => option.value === value); + + return ( + + + + + + + + + {loading ? "Loading..." : emptyText} + + {options.map((option) => ( + { + const newValue = currentValue === value ? "" : currentValue; + onValueChange?.(newValue); + setOpen(false); + }} + > + + {option.label} + + ))} + + + + + + ); +} \ No newline at end of file diff --git a/src/hooks/useAutocompleteData.ts b/src/hooks/useAutocompleteData.ts new file mode 100644 index 00000000..4b93e874 --- /dev/null +++ b/src/hooks/useAutocompleteData.ts @@ -0,0 +1,162 @@ +import { useState, useEffect } from 'react'; +import { supabase } from '@/integrations/supabase/client'; +import { ComboboxOption } from '@/components/ui/combobox'; + +export function useCountries() { + const [countries, setCountries] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + async function fetchCountries() { + setLoading(true); + try { + const { data, error } = await supabase + .from('locations') + .select('country') + .not('country', 'is', null); + + if (error) throw error; + + const uniqueCountries = Array.from( + new Set(data?.map(item => item.country) || []) + ).sort(); + + setCountries( + uniqueCountries.map(country => ({ + label: country, + value: country.toLowerCase().replace(/\s+/g, '_') + })) + ); + } catch (error) { + console.error('Error fetching countries:', error); + setCountries([]); + } finally { + setLoading(false); + } + } + + fetchCountries(); + }, []); + + return { countries, loading }; +} + +export function useStatesProvinces(country?: string) { + const [statesProvinces, setStatesProvinces] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (!country) { + setStatesProvinces([]); + return; + } + + async function fetchStatesProvinces() { + setLoading(true); + try { + const { data, error } = await supabase + .from('locations') + .select('state_province') + .eq('country', country) + .not('state_province', 'is', null); + + if (error) throw error; + + const uniqueStates = Array.from( + new Set(data?.map(item => item.state_province) || []) + ).sort(); + + setStatesProvinces( + uniqueStates.map(state => ({ + label: state, + value: state.toLowerCase().replace(/\s+/g, '_') + })) + ); + } catch (error) { + console.error('Error fetching states/provinces:', error); + setStatesProvinces([]); + } finally { + setLoading(false); + } + } + + fetchStatesProvinces(); + }, [country]); + + return { statesProvinces, loading }; +} + +export function useManufacturers() { + const [manufacturers, setManufacturers] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + async function fetchManufacturers() { + setLoading(true); + try { + const { data, error } = await supabase + .from('companies') + .select('name') + .eq('company_type', 'manufacturer') + .order('name'); + + if (error) throw error; + + setManufacturers( + (data || []).map(company => ({ + label: company.name, + value: company.name.toLowerCase().replace(/\s+/g, '_') + })) + ); + } catch (error) { + console.error('Error fetching manufacturers:', error); + setManufacturers([]); + } finally { + setLoading(false); + } + } + + fetchManufacturers(); + }, []); + + return { manufacturers, loading }; +} + +export function useCompanyHeadquarters() { + const [headquarters, setHeadquarters] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + async function fetchHeadquarters() { + setLoading(true); + try { + const { data, error } = await supabase + .from('companies') + .select('headquarters_location') + .not('headquarters_location', 'is', null); + + if (error) throw error; + + const uniqueHeadquarters = Array.from( + new Set(data?.map(item => item.headquarters_location) || []) + ).sort(); + + setHeadquarters( + uniqueHeadquarters.map(hq => ({ + label: hq, + value: hq.toLowerCase().replace(/\s+/g, '_') + })) + ); + } catch (error) { + console.error('Error fetching headquarters:', error); + setHeadquarters([]); + } finally { + setLoading(false); + } + } + + fetchHeadquarters(); + }, []); + + return { headquarters, loading }; +} \ No newline at end of file