143 lines
4.3 KiB
TypeScript
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
|
|
};
|
|
}
|
|
|