import { useState, useRef } from 'react'; import { searchUsers, ensureUserExists, type UserSummary } from '@/services/userApi'; /** * Custom Hook: useUserSearch * * Purpose: Manages user search functionality with debouncing * * Responsibilities: * - Handles user search with @ prefix * - Debounces search requests * - Manages search results and loading states * - Validates and ensures users exist in database */ export function useUserSearch() { const [searchResults, setSearchResults] = useState([]); const [searchLoading, setSearchLoading] = useState(false); const searchTimer = useRef(null); const searchUsersDebounced = async (searchTerm: string, limit: number = 10) => { if (searchTimer.current) { clearTimeout(searchTimer.current); } if (!searchTerm || !searchTerm.startsWith('@') || searchTerm.length < 2) { setSearchResults([]); setSearchLoading(false); return; } setSearchLoading(true); searchTimer.current = setTimeout(async () => { try { const term = searchTerm.slice(1); // remove leading '@' const response = await searchUsers(term, limit); const results = response.data?.data || []; setSearchResults(results); } catch (err) { console.error('User search failed:', err); setSearchResults([]); } finally { setSearchLoading(false); } }, 300); }; const clearSearch = () => { if (searchTimer.current) { clearTimeout(searchTimer.current); } setSearchResults([]); setSearchLoading(false); }; const ensureUser = async (user: UserSummary) => { try { const dbUser = await ensureUserExists({ userId: user.userId, email: user.email, displayName: user.displayName, firstName: user.firstName, lastName: user.lastName, department: user.department, phone: user.phone, mobilePhone: user.mobilePhone, designation: user.designation, jobTitle: user.jobTitle, manager: user.manager, employeeId: user.employeeId, employeeNumber: user.employeeNumber, secondEmail: user.secondEmail, location: user.location }); return dbUser; } catch (err) { console.error('Failed to ensure user exists:', err); throw err; } }; return { searchResults, searchLoading, searchUsersDebounced, clearSearch, ensureUser }; } /** * Custom Hook: useMultiUserSearch * * Purpose: Manages multiple independent user searches (e.g., for multiple approver fields) */ export function useMultiUserSearch() { const [userSearchResults, setUserSearchResults] = useState>({}); const [userSearchLoading, setUserSearchLoading] = useState>({}); const searchTimers = useRef>({}); const searchUsersForIndex = async (index: number, searchTerm: string, limit: number = 10) => { if (searchTimers.current[index]) { clearTimeout(searchTimers.current[index]); } if (!searchTerm || !searchTerm.startsWith('@') || searchTerm.length < 2) { setUserSearchResults(prev => ({ ...prev, [index]: [] })); setUserSearchLoading(prev => ({ ...prev, [index]: false })); return; } setUserSearchLoading(prev => ({ ...prev, [index]: true })); searchTimers.current[index] = setTimeout(async () => { try { const term = searchTerm.slice(1); const response = await searchUsers(term, limit); const results = response.data?.data || []; setUserSearchResults(prev => ({ ...prev, [index]: results })); } catch (err) { console.error(`User search failed for index ${index}:`, err); setUserSearchResults(prev => ({ ...prev, [index]: [] })); } finally { setUserSearchLoading(prev => ({ ...prev, [index]: false })); } }, 300); }; const clearSearchForIndex = (index: number) => { if (searchTimers.current[index]) { clearTimeout(searchTimers.current[index]); } setUserSearchResults(prev => ({ ...prev, [index]: [] })); setUserSearchLoading(prev => ({ ...prev, [index]: false })); }; return { userSearchResults, userSearchLoading, searchUsersForIndex, clearSearchForIndex }; }