import React, { useState, useEffect, useRef } from 'react'; import api from '../services/api'; import './IngredientAutocomplete.css'; interface IngredientSuggestion { name: string; count: number; } interface IngredientAutocompleteProps { value: string; onChange: (value: string) => void; placeholder?: string; className?: string; } const IngredientAutocomplete: React.FC = ({ value, onChange, placeholder = "Enter ingredient name", className = "" }) => { const [suggestions, setSuggestions] = useState([]); const [showSuggestions, setShowSuggestions] = useState(false); const [loading, setLoading] = useState(false); const [selectedIndex, setSelectedIndex] = useState(-1); const inputRef = useRef(null); const suggestionsRef = useRef(null); // Debounce function to avoid too many API calls const debounce = (func: Function, wait: number) => { let timeout: NodeJS.Timeout; return (...args: any[]) => { clearTimeout(timeout); timeout = setTimeout(() => func.apply(null, args), wait); }; }; // Fetch suggestions from API const fetchSuggestions = async (query: string) => { if (query.length < 2) { setSuggestions([]); setShowSuggestions(false); return; } setLoading(true); try { const response = await api.get(`/recipes/ingredients/suggestions?q=${encodeURIComponent(query)}`); const suggestionData = response.data as IngredientSuggestion[]; setSuggestions(suggestionData); setShowSuggestions(suggestionData.length > 0); setSelectedIndex(-1); } catch (error) { console.error('Error fetching ingredient suggestions:', error); setSuggestions([]); setShowSuggestions(false); } finally { setLoading(false); } }; // Debounced version of fetchSuggestions const debouncedFetchSuggestions = debounce(fetchSuggestions, 300); useEffect(() => { if (value) { debouncedFetchSuggestions(value); } else { setSuggestions([]); setShowSuggestions(false); } }, [value]); const handleInputChange = (e: React.ChangeEvent) => { const newValue = e.target.value; onChange(newValue); }; const handleSuggestionClick = (suggestion: IngredientSuggestion) => { onChange(suggestion.name); setShowSuggestions(false); setSelectedIndex(-1); inputRef.current?.focus(); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (!showSuggestions || suggestions.length === 0) return; switch (e.key) { case 'ArrowDown': e.preventDefault(); setSelectedIndex(prev => prev < suggestions.length - 1 ? prev + 1 : prev ); break; case 'ArrowUp': e.preventDefault(); setSelectedIndex(prev => prev > 0 ? prev - 1 : -1); break; case 'Enter': e.preventDefault(); if (selectedIndex >= 0) { handleSuggestionClick(suggestions[selectedIndex]); } break; case 'Escape': setShowSuggestions(false); setSelectedIndex(-1); break; } }; const handleBlur = (e: React.FocusEvent) => { // Delay hiding suggestions to allow for clicks setTimeout(() => { if (!suggestionsRef.current?.contains(e.relatedTarget as Node)) { setShowSuggestions(false); setSelectedIndex(-1); } }, 150); }; const handleFocus = () => { if (value && suggestions.length > 0) { setShowSuggestions(true); } }; return (
{loading && (
)}
{showSuggestions && suggestions.length > 0 && (
{suggestions.map((suggestion, index) => (
handleSuggestionClick(suggestion)} onMouseEnter={() => setSelectedIndex(index)} > {suggestion.name} {suggestion.count} recipe{suggestion.count !== 1 ? 's' : ''}
))}
)}
); }; export default IngredientAutocomplete;