151 lines
3.9 KiB
TypeScript
151 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useCallback, useRef } from "react";
|
|
|
|
/**
|
|
* useTableData — 통합 table 컴포넌트 데이터 fetch 훅
|
|
*
|
|
* entityJoinApi.getTableDataWithJoins() 호출.
|
|
* 페이지네이션, 정렬, 검색 상태를 관리.
|
|
*/
|
|
|
|
export interface UseTableDataParams {
|
|
tableName?: string;
|
|
page?: number;
|
|
pageSize?: number;
|
|
sortBy?: string;
|
|
sortOrder?: "asc" | "desc";
|
|
search?: Record<string, any>;
|
|
enabled?: boolean; // false면 fetch 안 함 (디자인 모드)
|
|
}
|
|
|
|
export interface UseTableDataResult {
|
|
data: Record<string, any>[];
|
|
total: number;
|
|
totalPages: number;
|
|
page: number;
|
|
pageSize: number;
|
|
sortBy: string;
|
|
sortOrder: "asc" | "desc";
|
|
loading: boolean;
|
|
error: string | null;
|
|
// 액션
|
|
setPage: (p: number) => void;
|
|
setPageSize: (s: number) => void;
|
|
setSortBy: (col: string) => void;
|
|
toggleSort: (col: string) => void;
|
|
setSearch: (s: Record<string, any>) => void;
|
|
refresh: () => void;
|
|
}
|
|
|
|
export function useTableData(params: UseTableDataParams): UseTableDataResult {
|
|
const {
|
|
tableName,
|
|
page: initialPage = 1,
|
|
pageSize: initialPageSize = 20,
|
|
sortBy: initialSortBy = "",
|
|
sortOrder: initialSortOrder = "desc",
|
|
search: externalSearch,
|
|
enabled = true,
|
|
} = params;
|
|
|
|
const [data, setData] = useState<Record<string, any>[]>([]);
|
|
const [total, setTotal] = useState(0);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [page, setPage] = useState(initialPage);
|
|
const [pageSize, setPageSize] = useState(initialPageSize);
|
|
const [sortBy, setSortBy] = useState(initialSortBy);
|
|
const [sortOrder, setSortOrder] = useState<"asc" | "desc">(initialSortOrder);
|
|
const [search, setSearch] = useState<Record<string, any>>(externalSearch || {});
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const refreshKey = useRef(0);
|
|
|
|
// 외부 검색 조건 동기화
|
|
useEffect(() => {
|
|
if (externalSearch) {
|
|
setSearch(externalSearch);
|
|
setPage(1);
|
|
}
|
|
}, [externalSearch]);
|
|
|
|
// 데이터 fetch
|
|
const fetchData = useCallback(async () => {
|
|
if (!tableName || !enabled) return;
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
// 동적 import — 번들 사이즈 최적화
|
|
const { entityJoinApi } = await import("@/lib/api/entityJoin");
|
|
|
|
const response = await entityJoinApi.getTableDataWithJoins(tableName, {
|
|
page,
|
|
size: pageSize,
|
|
sortBy: sortBy || undefined,
|
|
sortOrder,
|
|
search: Object.keys(search).length > 0 ? search : undefined,
|
|
enableEntityJoin: true,
|
|
});
|
|
|
|
setData(response.data || []);
|
|
setTotal(response.total || 0);
|
|
setTotalPages(response.totalPages || 1);
|
|
} catch (err: any) {
|
|
console.error("[useTableData] fetch 실패:", err);
|
|
setError(err?.message || "데이터 로드 실패");
|
|
setData([]);
|
|
setTotal(0);
|
|
setTotalPages(1);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [tableName, page, pageSize, sortBy, sortOrder, search, enabled, refreshKey.current]);
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, [fetchData]);
|
|
|
|
// 테이블 변경 시 페이지 리셋
|
|
useEffect(() => {
|
|
setPage(1);
|
|
setSortBy("");
|
|
setSearch({});
|
|
}, [tableName]);
|
|
|
|
const toggleSort = useCallback((col: string) => {
|
|
setSortBy((prev) => {
|
|
if (prev === col) {
|
|
setSortOrder((o) => (o === "asc" ? "desc" : "asc"));
|
|
return col;
|
|
}
|
|
setSortOrder("asc");
|
|
return col;
|
|
});
|
|
}, []);
|
|
|
|
const refresh = useCallback(() => {
|
|
refreshKey.current += 1;
|
|
fetchData();
|
|
}, [fetchData]);
|
|
|
|
return {
|
|
data,
|
|
total,
|
|
totalPages,
|
|
page,
|
|
pageSize,
|
|
sortBy,
|
|
sortOrder,
|
|
loading,
|
|
error,
|
|
setPage,
|
|
setPageSize: (s: number) => { setPageSize(s); setPage(1); },
|
|
setSortBy,
|
|
toggleSort,
|
|
setSearch: (s: Record<string, any>) => { setSearch(s); setPage(1); },
|
|
refresh,
|
|
};
|
|
}
|