73 lines
3.0 KiB
TypeScript
73 lines
3.0 KiB
TypeScript
import { useEffect, useRef, useState } from 'react';
|
|
|
|
// 서버사이드 리스트(검색·필터·페이지네이션)의 UI 상태 단일 출처.
|
|
// 실제 데이터 패칭은 각 도메인 훅(useProducts/usePartners 등)이 이 상태로
|
|
// - 검색/필터가 바뀌면 page 를 1 로 리셋한다(다른 결과셋의 동일 페이지로 점프 방지).
|
|
export type ServerListControls = {
|
|
page: number;
|
|
setPage: (p: number) => void;
|
|
pageSize: number;
|
|
search: string; // input value (controlled)
|
|
setSearch: (v: string) => void;
|
|
submitSearch: () => void; // 엔터/즉시 검색용 (디바운스·최소길이 무시하고 바로 발사)
|
|
clearSearch: () => void; // X 버튼: 입력 비우고 즉시 전체 재검색
|
|
debouncedSearch: string; // 쿼리 파라미터용 (디바운스 적용)
|
|
filters: Record<string, string>;
|
|
setFilter: (key: string, value: string) => void;
|
|
totalPages: (total: number) => number; // 서버 total → 페이지 수
|
|
};
|
|
|
|
export function useServerList(opts?: {
|
|
pageSize?: number;
|
|
initialFilters?: Record<string, string>;
|
|
debounceMs?: number;
|
|
minSearchLength?: number;
|
|
}): ServerListControls {
|
|
const pageSize = opts?.pageSize ?? 10;
|
|
const debounceMs = opts?.debounceMs ?? 500;
|
|
const minSearchLength = opts?.minSearchLength ?? 2;
|
|
|
|
const [page, setPage] = useState(1);
|
|
const [search, setSearchInput] = useState('');
|
|
const [debouncedSearch, setDebouncedSearch] = useState('');
|
|
const [filters, setFilters] = useState<Record<string, string>>(() => opts?.initialFilters ?? {});
|
|
const timerRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
|
|
|
// 입력 디바운스 → 쿼리용 검색어.
|
|
// 최소 길이 미만은 빈 검색(전체)으로 둬서 1글자 스캔 요청이 서버로 나가지 않게 막는다.
|
|
useEffect(() => {
|
|
timerRef.current = setTimeout(() => {
|
|
const q = search.trim();
|
|
setDebouncedSearch(q.length >= minSearchLength ? q : '');
|
|
}, debounceMs);
|
|
return () => clearTimeout(timerRef.current);
|
|
}, [search, debounceMs, minSearchLength]);
|
|
|
|
const setSearch = (v: string) => {
|
|
setSearchInput(v);
|
|
setPage(1);
|
|
};
|
|
|
|
// 엔터: 대기 중인 디바운스 타이머를 버리고 최소길이 무시하고 즉시 1회 발사(의도적 검색).
|
|
const submitSearch = () => {
|
|
clearTimeout(timerRef.current);
|
|
setDebouncedSearch(search.trim());
|
|
setPage(1);
|
|
};
|
|
// X 버튼: 입력·디바운스 검색어를 즉시 비우고(대기 타이머 취소) 1페이지로 → 전체 목록 재조회.
|
|
const clearSearch = () => {
|
|
clearTimeout(timerRef.current);
|
|
setSearchInput('');
|
|
setDebouncedSearch('');
|
|
setPage(1);
|
|
};
|
|
const setFilter = (key: string, value: string) => {
|
|
setFilters((f) => ({ ...f, [key]: value }));
|
|
setPage(1);
|
|
};
|
|
|
|
const totalPages = (total: number) => Math.max(1, Math.ceil(total / pageSize));
|
|
|
|
return { page, setPage, pageSize, search, setSearch, submitSearch, clearSearch, debouncedSearch, filters, setFilter, totalPages };
|
|
}
|