import React from "react"; import Box from "@mui/material/Box"; import TextField from "@mui/material/TextField"; import InputAdornment from "@mui/material/InputAdornment"; import Paper from "@mui/material/Paper"; import List from "@mui/material/List"; import ListItem from "@mui/material/ListItem"; import ListItemText from "@mui/material/ListItemText"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; import SearchIcon from "@mui/icons-material/Search"; import KeyboardReturnIcon from "@mui/icons-material/KeyboardReturn"; import { useNavigate, useLocation } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { LanguageContext } from "../../i18n/withTranslation.js"; const SearchBar = () => { const navigate = useNavigate(); const location = useLocation(); const searchParams = new URLSearchParams(location.search); const { t, i18n } = useTranslation(); const languageContext = React.useContext(LanguageContext); // State management const [searchQuery, setSearchQuery] = React.useState( searchParams.get("q") || "" ); const [suggestions, setSuggestions] = React.useState([]); const [showSuggestions, setShowSuggestions] = React.useState(false); const [selectedIndex, setSelectedIndex] = React.useState(-1); // Refs for debouncing and timers const debounceTimerRef = React.useRef(null); const autocompleteTimerRef = React.useRef(null); const isFirstKeystrokeRef = React.useRef(true); const inputRef = React.useRef(null); const suggestionBoxRef = React.useRef(null); const handleSearch = (e) => { e.preventDefault(); delete window.currentSearchQuery; setShowSuggestions(false); if (searchQuery.trim()) { navigate(`/search?q=${encodeURIComponent(searchQuery)}`); } }; const updateSearchState = (value) => { setSearchQuery(value); // Dispatch global custom event with search query value const searchEvent = new CustomEvent("search-query-change", { detail: { query: value }, }); // Store the current search query in the window object window.currentSearchQuery = value; window.dispatchEvent(searchEvent); }; // @note Autocomplete function using getSearchProducts Socket.io API - returns objects with name and seoName const fetchAutocomplete = React.useCallback( (query) => { if (!query || query.length < 2) { setSuggestions([]); setShowSuggestions(false); return; } const currentLanguage = languageContext?.currentLanguage || i18n?.language || 'de'; window.socketManager.emit( "getSearchProducts", { query: query.trim(), limit: 8, language: currentLanguage, requestTranslation: currentLanguage === 'de' ? false : true, }, (response) => { if (response && response.products) { // getSearchProducts returns response.products array const suggestions = response.products.slice(0, 8); // Limit to 8 suggestions setSuggestions(suggestions); setShowSuggestions(suggestions.length > 0); setSelectedIndex(-1); // Reset selection } else { setSuggestions([]); setShowSuggestions(false); console.log("getSearchProducts failed or no products:", response); } } ); }, [languageContext, i18n] ); const handleSearchChange = (e) => { const value = e.target.value; // Always update the input field immediately for responsiveness setSearchQuery(value); // Clear any existing timers if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } if (autocompleteTimerRef.current) { clearTimeout(autocompleteTimerRef.current); } // Set the debounce timer for search state update const delay = isFirstKeystrokeRef.current ? 100 : 200; debounceTimerRef.current = setTimeout(() => { updateSearchState(value); isFirstKeystrokeRef.current = false; // Reset first keystroke flag after 1 second of inactivity debounceTimerRef.current = setTimeout(() => { isFirstKeystrokeRef.current = true; }, 1000); }, delay); // Set autocomplete timer with longer delay to reduce API calls autocompleteTimerRef.current = setTimeout(() => { fetchAutocomplete(value); }, 300); }; // Handle keyboard navigation in suggestions const handleKeyDown = (e) => { 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 && selectedIndex < suggestions.length) { const selectedSuggestion = suggestions[selectedIndex]; setSearchQuery(selectedSuggestion.name); updateSearchState(selectedSuggestion.name); setShowSuggestions(false); navigate(`/Artikel/${selectedSuggestion.seoName}`); } else { handleSearch(e); } break; case "Escape": setShowSuggestions(false); setSelectedIndex(-1); inputRef.current?.blur(); break; } }; // Handle suggestion click - navigate to product page directly const handleSuggestionClick = (suggestion) => { setSearchQuery(suggestion.name); updateSearchState(suggestion.name); setShowSuggestions(false); navigate(`/Artikel/${suggestion.seoName}`); }; // Handle input focus const handleFocus = () => { if (suggestions.length > 0 && searchQuery.length >= 2) { setShowSuggestions(true); } }; // Handle input blur with delay to allow suggestion clicks const handleBlur = () => { setTimeout(() => { setShowSuggestions(false); setSelectedIndex(-1); }, 200); }; // Get delivery days based on availability const getDeliveryDays = (product) => { if (product.available === 1) { return t('delivery.times.standard2to3Days'); } else if (product.incoming === 1 || product.availableSupplier === 1) { return t('delivery.times.supplier7to9Days'); } }; // Handle enter icon click const handleEnterClick = () => { delete window.currentSearchQuery; setShowSuggestions(false); if (searchQuery.trim()) { navigate(`/search?q=${encodeURIComponent(searchQuery)}`); } }; // Clean up timers on unmount React.useEffect(() => { return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } if (autocompleteTimerRef.current) { clearTimeout(autocompleteTimerRef.current); } }; }, []); // Close suggestions when clicking outside React.useEffect(() => { const handleClickOutside = (event) => { if ( suggestionBoxRef.current && !suggestionBoxRef.current.contains(event.target) && !inputRef.current?.contains(event.target) ) { setShowSuggestions(false); setSelectedIndex(-1); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); return ( ), endAdornment: ( ), sx: { borderRadius: 2, bgcolor: "background.paper" }, }} /> {/* Autocomplete Suggestions Dropdown */} {showSuggestions && suggestions.length > 0 && ( {suggestions.map((suggestion, index) => ( handleSuggestionClick(suggestion)} sx={{ cursor: "pointer", border: "none", background: "none", padding: 0, margin: 0, width: "100%", textAlign: "left", px: 2, // Add horizontal padding back "&:hover": { backgroundColor: "action.hover", }, "&.Mui-selected": { backgroundColor: "action.selected", "&:hover": { backgroundColor: "action.selected", }, }, py: 1, }} > {suggestion.name} {getDeliveryDays(suggestion)} {new Intl.NumberFormat('de-DE', {style: 'currency', currency: 'EUR'}).format(suggestion.price)} {t('product.inclVat', { vat: suggestion.vat })} } /> ))} {t('common.more')} )} ); }; export default SearchBar;