import React, {useEffect, useMemo, useRef, useState} from 'react'; import {Box, Button, CloseButton, Heading, Input, InputGroup, Spinner, Stack, Table, Text} from "@chakra-ui/react"; import {useAppStore} from "../appstore"; import {B2BQOMOrderListItem} from "../types/b2bqom"; import {UI_C} from "../constants/styling"; import {api} from "../api"; import {highlightSearchTerm} from "../utils/rendering"; import {toast} from "../components/ui/toaster"; import {Tooltip} from "../components/tooltip"; const DEBOUNCE_DELAY = 400; // ms const MIN_SEARCH_LENGTH = 2; export function SearchDialogContent() { const { searchTerm, searchResults, orderList, setSearchTerm, setSearchResults, clearSearchResults, searchTermOnSearchAction, showEmptyResultsMsg, addToOrderList } = useAppStore(); const inputRef = useRef(null); const [searchInProgress, setSearchInProgress] = useState(false); const searchIdRef = useRef(0); const debounceTimerRef = useRef(null); const search = async (term: string) => { if (!term || term.length < MIN_SEARCH_LENGTH) return; const searchId = ++searchIdRef.current; // bump counter setSearchInProgress(true); try { const results = await api.search(term); if (searchId === searchIdRef.current) { // Only apply if it's the latest search setSearchResults(term, results); } } catch(err) { toast({ type: "error", title: err instanceof Error ? err.message : "Please try again", }); } finally { if (searchId === searchIdRef.current) { setSearchInProgress(false); } } }; // Debounced search on typing useEffect(() => { // Clear previous timer if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } // Don't search if less than min length if (searchTerm.length < MIN_SEARCH_LENGTH) { return; } // Set new timer debounceTimerRef.current = window.setTimeout(() => { search(searchTerm); }, DEBOUNCE_DELAY); // Cleanup on unmount or before next effect return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current); } }; }, [searchTerm]); const orderListBySku = useMemo(() => { return new Map(orderList.map(oli => [oli.sku, oli])); }, [orderList]); const orderListCandidatesMap: Map = useMemo(() => { return new Map(searchResults.map(sr => ([sr.sku, {...sr, quantity: orderListBySku.get(sr.sku)?.quantity || 0}]))); }, [searchResults]); const addCandidateToList = (sku: string) => { const orderListCandidate = orderListCandidatesMap.get(sku); orderListCandidate && addToOrderList(orderListCandidate); inputRef?.current?.focus(); } return ( { searchInProgress && } }> setSearchTerm(e.target.value)} autoFocus={true} ref={inputRef} /> { (searchResults.length !== 0 || showEmptyResultsMsg) && Search Results for {searchTermOnSearchAction} { showEmptyResultsMsg ? Nothing found. Type another search term and press Enter. : SKU Product In stock Price   {Array.from(orderListCandidatesMap.values()).map((item) => ( {highlightSearchTerm(item.sku, searchTermOnSearchAction)} {highlightSearchTerm(item.name, searchTermOnSearchAction)} {item.stock_quantity} {item.price} { orderListBySku.has(item.sku) ? : } ))} } } ); }