Files
tfm_ainventory/frontend/hooks/useItemSearch.ts

113 lines
2.7 KiB
TypeScript

'use client';
import { useState, useCallback, useRef, useEffect } from 'react';
interface SearchItem {
id: number;
name: string;
part_number?: string;
barcode?: string;
quantity: number;
}
interface UseItemSearchResult {
results: SearchItem[];
isLoading: boolean;
error: string | null;
}
export function useItemSearch(
query: string,
enabled: boolean = true
): UseItemSearchResult {
const [results, setResults] = useState<SearchItem[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
const cacheRef = useRef<Map<string, SearchItem[]>>(new Map());
const performSearch = useCallback(async (searchQuery: string) => {
// Client-side validation: require min 2 chars
if (!searchQuery || searchQuery.length < 2) {
setResults([]);
setError(null);
return;
}
// Check cache first
if (cacheRef.current.has(searchQuery)) {
setResults(cacheRef.current.get(searchQuery) || []);
setError(null);
return;
}
setIsLoading(true);
setError(null);
try {
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8906';
const response = await fetch(`${backendUrl}/items/search?q=${encodeURIComponent(searchQuery)}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Search failed');
}
const data = await response.json();
const items = data || [];
// Cache the results
cacheRef.current.set(searchQuery, items);
setResults(items);
setError(null);
} catch (err) {
const errorMsg = err instanceof Error ? err.message : 'Search failed';
setError(errorMsg);
setResults([]);
} finally {
setIsLoading(false);
}
}, []);
// Debounced search on query change
useEffect(() => {
if (!enabled) {
setResults([]);
setError(null);
return;
}
// Clear previous debounce timer
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
// Set new debounce timer (300ms)
debounceTimerRef.current = setTimeout(() => {
performSearch(query);
}, 300);
return () => {
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
};
}, [query, enabled, performSearch]);
// Cleanup cache on unmount
useEffect(() => {
return () => {
cacheRef.current.clear();
if (debounceTimerRef.current) {
clearTimeout(debounceTimerRef.current);
}
};
}, []);
return { results, isLoading, error };
}