111 lines
3.1 KiB
TypeScript
111 lines
3.1 KiB
TypeScript
import { useState, useCallback, useEffect, useRef } from "react";
|
|
import { apiClient } from "@/lib/api/client";
|
|
import { EntitySearchResult, EntitySearchResponse } from "./types";
|
|
|
|
interface UseEntitySearchProps {
|
|
tableName: string;
|
|
searchFields?: string[];
|
|
filterCondition?: Record<string, any>;
|
|
}
|
|
|
|
export function useEntitySearch({
|
|
tableName,
|
|
searchFields = [],
|
|
filterCondition = {},
|
|
}: UseEntitySearchProps) {
|
|
const [searchText, setSearchText] = useState("");
|
|
const [results, setResults] = useState<EntitySearchResult[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [pagination, setPagination] = useState({
|
|
total: 0,
|
|
page: 1,
|
|
limit: 20,
|
|
});
|
|
|
|
// searchFields와 filterCondition을 ref로 관리하여 useCallback 의존성 문제 해결
|
|
const searchFieldsRef = useRef(searchFields);
|
|
const filterConditionRef = useRef(filterCondition);
|
|
|
|
useEffect(() => {
|
|
searchFieldsRef.current = searchFields;
|
|
filterConditionRef.current = filterCondition;
|
|
}, [searchFields, filterCondition]);
|
|
|
|
const search = useCallback(
|
|
async (text: string, page: number = 1) => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const params = new URLSearchParams({
|
|
searchText: text,
|
|
searchFields: searchFieldsRef.current.join(","),
|
|
filterCondition: JSON.stringify(filterConditionRef.current),
|
|
page: page.toString(),
|
|
limit: pagination.limit.toString(),
|
|
});
|
|
|
|
const response = await apiClient.get<EntitySearchResponse>(
|
|
`/entity-search/${tableName}?${params.toString()}`
|
|
);
|
|
|
|
if (response.data.success) {
|
|
setResults(response.data.data);
|
|
if (response.data.pagination) {
|
|
setPagination(response.data.pagination);
|
|
}
|
|
} else {
|
|
setError(response.data.error || "검색에 실패했습니다");
|
|
}
|
|
} catch (err: any) {
|
|
console.error("Entity search error:", err);
|
|
setError(err.response?.data?.message || "검색 중 오류가 발생했습니다");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
},
|
|
[tableName, pagination.limit]
|
|
);
|
|
|
|
// 디바운스된 검색
|
|
useEffect(() => {
|
|
// searchText가 명시적으로 설정되지 않은 경우(null/undefined)만 건너뛰기
|
|
if (searchText === null || searchText === undefined) {
|
|
return;
|
|
}
|
|
|
|
const timer = setTimeout(() => {
|
|
// 빈 문자열("")도 검색 (전체 목록 조회)
|
|
search(searchText.trim(), 1);
|
|
}, 300); // 300ms 디바운스
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [searchText, search]);
|
|
|
|
const clearSearch = useCallback(() => {
|
|
setSearchText("");
|
|
setResults([]);
|
|
setError(null);
|
|
}, []);
|
|
|
|
const loadMore = useCallback(() => {
|
|
if (pagination.page * pagination.limit < pagination.total) {
|
|
search(searchText, pagination.page + 1);
|
|
}
|
|
}, [search, searchText, pagination]);
|
|
|
|
return {
|
|
searchText,
|
|
setSearchText,
|
|
results,
|
|
loading,
|
|
error,
|
|
pagination,
|
|
search,
|
|
clearSearch,
|
|
loadMore,
|
|
};
|
|
}
|
|
|