Re_Figma_Code/src/hooks/useUserSearch.ts

143 lines
4.3 KiB
TypeScript

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<UserSummary[]>([]);
const [searchLoading, setSearchLoading] = useState(false);
const searchTimer = useRef<any>(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<Record<number, UserSummary[]>>({});
const [userSearchLoading, setUserSearchLoading] = useState<Record<number, boolean>>({});
const searchTimers = useRef<Record<number, any>>({});
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
};
}