diff options
Diffstat (limited to 'hooks')
| -rw-r--r-- | hooks/use-data-table copy.ts | 334 | ||||
| -rw-r--r-- | hooks/use-data-table.ts | 315 |
2 files changed, 590 insertions, 59 deletions
diff --git a/hooks/use-data-table copy.ts b/hooks/use-data-table copy.ts new file mode 100644 index 00000000..a3301067 --- /dev/null +++ b/hooks/use-data-table copy.ts @@ -0,0 +1,334 @@ +"use client" + +import * as React from "react" +import type { DataTableFilterField, ExtendedSortingState } from "@/types/table" +import { + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + getGroupedRowModel, + getExpandedRowModel, + useReactTable, + type ColumnFiltersState, + type PaginationState, + type RowSelectionState, + type SortingState, + type TableOptions, + type TableState, + type Updater, + type VisibilityState, + type ExpandedState, +} from "@tanstack/react-table" +import { + parseAsArrayOf, + parseAsInteger, + parseAsString, + useQueryState, + useQueryStates, + type Parser, + type UseQueryStateOptions, +} from "nuqs" + +import { getSortingStateParser } from "@/lib/parsers" +import { useDebouncedCallback } from "@/hooks/use-debounced-callback" + +interface UseDataTableProps<TData> + extends Omit< + TableOptions<TData>, + | "state" + | "pageCount" + | "getCoreRowModel" + | "manualFiltering" + | "manualPagination" + | "manualSorting" + | "onGroupingChange" + | "onExpandedChange" + | "getExpandedRowModel" + >, + Required<Pick<TableOptions<TData>, "pageCount">> { + filterFields?: DataTableFilterField<TData>[] + enableAdvancedFilter?: boolean + history?: "push" | "replace" + scroll?: boolean + shallow?: boolean + throttleMs?: number + debounceMs?: number + startTransition?: React.TransitionStartFunction + clearOnDefault?: boolean + initialState?: Omit<Partial<TableState>, "sorting"> & { + sorting?: ExtendedSortingState<TData> + /** + * 기본 그룹핑 컬럼 배열 (멀티 그룹핑) + */ + grouping?: string[] + /** + * 그룹 확장/접기 상태 + */ + expanded?: Record<string, boolean> + } +} + +export function useDataTable<TData>({ + pageCount = -1, + filterFields = [], + enableAdvancedFilter = false, + history = "replace", + scroll = false, + shallow = true, + throttleMs = 50, + debounceMs = 300, + clearOnDefault = false, + startTransition, + initialState, + ...props +}: UseDataTableProps<TData>) { + // 공통 URL QueryState 옵션 + const queryStateOptions = React.useMemo< + Omit<UseQueryStateOptions<string>, "parse"> + >(() => { + return { + history, + scroll, + shallow, + throttleMs, + debounceMs, + clearOnDefault, + startTransition, + } + }, [ + history, + scroll, + shallow, + throttleMs, + debounceMs, + clearOnDefault, + startTransition, + ]) + + // -------- RowSelection & ColumnVisibility는 URL 동기화 없이 로컬 상태 ---------- + const [rowSelection, setRowSelection] = React.useState<RowSelectionState>( + initialState?.rowSelection ?? {} + ) + const [columnVisibility, setColumnVisibility] = + React.useState<VisibilityState>(initialState?.columnVisibility ?? {}) + + // -------- Pagination (page, perPage) URL 동기화 -------- + const [page, setPage] = useQueryState( + "page", + parseAsInteger.withOptions(queryStateOptions).withDefault(1) + ) + const [perPage, setPerPage] = useQueryState( + "perPage", + parseAsInteger + .withOptions(queryStateOptions) + .withDefault(initialState?.pagination?.pageSize ?? 10) + ) + + // -------- Sorting (sort) URL 동기화 -------- + const [sorting, setSorting] = useQueryState( + "sort", + getSortingStateParser<TData>() + .withOptions(queryStateOptions) + .withDefault(initialState?.sorting ?? []) + ) + function onSortingChange(updaterOrValue: Updater<SortingState>) { + if (typeof updaterOrValue === "function") { + const newSorting = updaterOrValue(sorting) as ExtendedSortingState<TData> + void setSorting(newSorting) + } else { + void setSorting(updaterOrValue as ExtendedSortingState<TData>) + } + } + + // -------- Grouping (group) URL 동기화 (멀티 컬럼) -------- + const [grouping, setGrouping] = useQueryState( + "group", + parseAsArrayOf(parseAsString, ",") + .withOptions(queryStateOptions) + .withDefault(initialState?.grouping ?? []) + ) + function onGroupingChange(updaterOrValue: Updater<string[]>) { + if (typeof updaterOrValue === "function") { + const newGrouping = updaterOrValue(grouping) + void setGrouping(newGrouping) + } else { + void setGrouping(updaterOrValue) + } + } + + // -------- Group Expand/Collapse -------- + const [expanded, setExpanded] = React.useState<ExpandedState>({}) // or true/false + + function onExpandedChange(updater: Updater<ExpandedState>) { + setExpanded((old) => (typeof updater === "function" ? updater(old) : updater)) + } + + // -------- Filters (search/faceted) URL 동기화 -------- + const filterParsers = React.useMemo(() => { + return filterFields.reduce< + Record<string, Parser<string> | Parser<string[]>> + >((acc, field) => { + if (field.options) { + // Faceted filter -> 여러 값 가능 + acc[field.id] = parseAsArrayOf(parseAsString, ",").withOptions( + queryStateOptions + ) + } else { + // Search filter -> 단일 값 + acc[field.id] = parseAsString.withOptions(queryStateOptions) + } + return acc + }, {}) + }, [filterFields, queryStateOptions]) + + const [filterValues, setFilterValues] = useQueryStates(filterParsers) + const debouncedSetFilterValues = useDebouncedCallback( + setFilterValues, + debounceMs + ) + + // -------- PaginationState 객체 -------- + const pagination: PaginationState = { + pageIndex: page - 1, + pageSize: perPage, + } + function onPaginationChange(updaterOrValue: Updater<PaginationState>) { + if (typeof updaterOrValue === "function") { + const newPagination = updaterOrValue(pagination) + void setPage(newPagination.pageIndex + 1) + void setPerPage(newPagination.pageSize) + } else { + void setPage(updaterOrValue.pageIndex + 1) + void setPerPage(updaterOrValue.pageSize) + } + } + + // -------- ColumnFiltersState -------- + const initialColumnFilters: ColumnFiltersState = React.useMemo(() => { + // AdvancedFilter 모드가 아니면 URL에서 온 filterValues를 그대로 적용 + return enableAdvancedFilter + ? [] + : Object.entries(filterValues).reduce<ColumnFiltersState>( + (filters, [key, value]) => { + if (value !== null) { + filters.push({ + id: key, + value: Array.isArray(value) ? value : [value], + }) + } + return filters + }, + [] + ) + }, [filterValues, enableAdvancedFilter]) + + const [columnFilters, setColumnFilters] = + React.useState<ColumnFiltersState>(initialColumnFilters) + + // 검색용 / Facet 필터용 컬럼 구분 + const { searchableColumns, filterableColumns } = React.useMemo(() => { + return enableAdvancedFilter + ? { searchableColumns: [], filterableColumns: [] } + : { + searchableColumns: filterFields.filter((field) => !field.options), + filterableColumns: filterFields.filter((field) => field.options), + } + }, [filterFields, enableAdvancedFilter]) + + const onColumnFiltersChange = React.useCallback( + (updaterOrValue: Updater<ColumnFiltersState>) => { + if (enableAdvancedFilter) return + setColumnFilters((prev) => { + const next = + typeof updaterOrValue === "function" + ? updaterOrValue(prev) + : updaterOrValue + + const filterUpdates = next.reduce< + Record<string, string | string[] | null> + >((acc, filter) => { + if (searchableColumns.find((col) => col.id === filter.id)) { + acc[filter.id] = filter.value as string + } else if (filterableColumns.find((col) => col.id === filter.id)) { + acc[filter.id] = filter.value as string[] + } + return acc + }, {}) + + // 빠진 필터는 null로 설정 + prev.forEach((prevFilter) => { + if (!next.some((filter) => filter.id === prevFilter.id)) { + filterUpdates[prevFilter.id] = null + } + }) + + // 필터가 바뀌면 첫 페이지로 + void setPage(1) + debouncedSetFilterValues(filterUpdates) + return next + }) + }, + [ + debouncedSetFilterValues, + enableAdvancedFilter, + filterableColumns, + searchableColumns, + setPage, + ] + ) + + // -------- TanStack Table 인스턴스 생성 -------- + const table = useReactTable({ + ...props, + initialState, + pageCount, + state: { + pagination, + sorting, + columnVisibility, + rowSelection, + columnFilters: enableAdvancedFilter ? [] : columnFilters, + grouping, + expanded, + }, + + // 콜백들 + onRowSelectionChange: setRowSelection, + onPaginationChange, + onSortingChange, + onColumnFiltersChange, + onColumnVisibilityChange: setColumnVisibility, + onGroupingChange, + onExpandedChange, + + // 기본 모델 + getCoreRowModel: getCoreRowModel(), + // 필터 (Advanced 모드 아니면 사용) + getFilteredRowModel: enableAdvancedFilter ? undefined : getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + // 그룹 + 확장 + getGroupedRowModel: getGroupedRowModel(), + getExpandedRowModel: getExpandedRowModel(), + + // Faceted (Facet 필터용) + getFacetedRowModel: enableAdvancedFilter ? undefined : getFacetedRowModel(), + getFacetedUniqueValues: enableAdvancedFilter + ? undefined + : getFacetedUniqueValues(), + + // 서버 사이드 사용 + manualPagination: true, + manualSorting: true, + manualFiltering: true, + // 그룹핑도 서버에서 처리한다면 별도 로직이 필요하지만, + // TanStack Table v8에는 manualGrouping 옵션은 없음 + // (그룹핑을 서버에서 이미 해서 내려받는다면 row 구조 처리 필요) + + }) + + return { table } +}
\ No newline at end of file diff --git a/hooks/use-data-table.ts b/hooks/use-data-table.ts index a3301067..f33c2e8b 100644 --- a/hooks/use-data-table.ts +++ b/hooks/use-data-table.ts @@ -1,3 +1,4 @@ +// hooks/use-data-table.ts (업데이트) "use client" import * as React from "react" @@ -31,10 +32,21 @@ import { type Parser, type UseQueryStateOptions, } from "nuqs" +import useSWRInfinite from "swr/infinite" import { getSortingStateParser } from "@/lib/parsers" import { useDebouncedCallback } from "@/hooks/use-debounced-callback" +// 무한 스크롤 임계값 (이 값 이상이면 무한 스크롤 모드) +const INFINITE_SCROLL_THRESHOLD = 1_000_000 + +// 무한 스크롤 설정 +interface InfiniteScrollConfig { + apiEndpoint: string + tableName: string + maxPageSize?: number +} + interface UseDataTableProps<TData> extends Omit< TableOptions<TData>, @@ -60,15 +72,22 @@ interface UseDataTableProps<TData> clearOnDefault?: boolean initialState?: Omit<Partial<TableState>, "sorting"> & { sorting?: ExtendedSortingState<TData> - /** - * 기본 그룹핑 컬럼 배열 (멀티 그룹핑) - */ grouping?: string[] - /** - * 그룹 확장/접기 상태 - */ expanded?: Record<string, boolean> } + // 기존 데이터 (페이지네이션 모드용) + data?: TData[] + // 무한 스크롤 설정 (pageSize 기반 자동 활성화) + infiniteScrollConfig?: InfiniteScrollConfig +} + +// 무한 스크롤 응답 타입 +interface InfiniteScrollResponse<TData> { + mode: 'infinite' + data: TData[] + hasNextPage: boolean + nextCursor: string | null + total?: number | null } export function useDataTable<TData>({ @@ -83,8 +102,11 @@ export function useDataTable<TData>({ clearOnDefault = false, startTransition, initialState, + data: initialData = [], + infiniteScrollConfig, ...props }: UseDataTableProps<TData>) { + // 공통 URL QueryState 옵션 const queryStateOptions = React.useMemo< Omit<UseQueryStateOptions<string>, "parse"> @@ -108,14 +130,14 @@ export function useDataTable<TData>({ startTransition, ]) - // -------- RowSelection & ColumnVisibility는 URL 동기화 없이 로컬 상태 ---------- + // -------- 기존 상태들 -------- const [rowSelection, setRowSelection] = React.useState<RowSelectionState>( initialState?.rowSelection ?? {} ) const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(initialState?.columnVisibility ?? {}) - // -------- Pagination (page, perPage) URL 동기화 -------- + // -------- Pagination URL 동기화 -------- const [page, setPage] = useQueryState( "page", parseAsInteger.withOptions(queryStateOptions).withDefault(1) @@ -127,13 +149,208 @@ export function useDataTable<TData>({ .withDefault(initialState?.pagination?.pageSize ?? 10) ) - // -------- Sorting (sort) URL 동기화 -------- + // pageSize 기반 무한 스크롤 모드 자동 결정 + const isInfiniteMode = perPage >= INFINITE_SCROLL_THRESHOLD + + // -------- Sorting URL 동기화 -------- const [sorting, setSorting] = useQueryState( "sort", getSortingStateParser<TData>() .withOptions(queryStateOptions) .withDefault(initialState?.sorting ?? []) ) + + // -------- Advanced Filters URL 동기화 -------- + const [filters, setFilters] = useQueryState( + "filters", + parseAsString.withOptions(queryStateOptions).withDefault("[]") + ) + + const [joinOperator, setJoinOperator] = useQueryState( + "joinOperator", + parseAsString.withOptions(queryStateOptions).withDefault("and") + ) + + const [search, setSearch] = useQueryState( + "search", + parseAsString.withOptions(queryStateOptions).withDefault("") + ) + + // -------- Grouping URL 동기화 -------- + const [grouping, setGrouping] = useQueryState( + "group", + parseAsArrayOf(parseAsString, ",") + .withOptions(queryStateOptions) + .withDefault(initialState?.grouping ?? []) + ) + + const [expanded, setExpanded] = React.useState<ExpandedState>({}) + + // -------- 무한 스크롤 SWR 설정 -------- + const parsedFilters = React.useMemo(() => { + try { + return JSON.parse(filters) + } catch { + return [] + } + }, [filters]) + + const sortForSWR = React.useMemo(() => { + return sorting.map(sort => ({ + id: sort.id, + desc: sort.desc + })) + }, [sorting]) + + // 실제 페이지 크기 계산 (무한 스크롤 시) + const effectivePageSize = React.useMemo(() => { + if (!isInfiniteMode) return perPage + + // 무한 스크롤 모드에서는 적절한 청크 크기 사용 + const maxSize = infiniteScrollConfig?.maxPageSize || 100 + return Math.min(50, maxSize) // 기본 50개씩 로드 + }, [isInfiniteMode, perPage, infiniteScrollConfig?.maxPageSize]) + + // SWR 키 생성 함수 + const getKey = React.useCallback( + (pageIndex: number, previousPageData: InfiniteScrollResponse<TData> | null) => { + if (!isInfiniteMode || !infiniteScrollConfig) return null + + const params = new URLSearchParams() + + if (pageIndex === 0) { + // 첫 페이지 + params.set("limit", String(effectivePageSize)) + if (search) params.set("search", search) + if (parsedFilters.length) params.set("filters", JSON.stringify(parsedFilters)) + if (joinOperator !== "and") params.set("joinOperator", joinOperator) + if (sortForSWR.length) params.set("sort", JSON.stringify(sortForSWR)) + + return `${infiniteScrollConfig.apiEndpoint}?${params.toString()}` + } + + // 다음 페이지 + if (!previousPageData || !previousPageData.hasNextPage) return null + + params.set("cursor", previousPageData.nextCursor || "") + params.set("limit", String(effectivePageSize)) + if (search) params.set("search", search) + if (parsedFilters.length) params.set("filters", JSON.stringify(parsedFilters)) + if (joinOperator !== "and") params.set("joinOperator", joinOperator) + if (sortForSWR.length) params.set("sort", JSON.stringify(sortForSWR)) + + return `${infiniteScrollConfig.apiEndpoint}?${params.toString()}` + }, + [isInfiniteMode, infiniteScrollConfig, effectivePageSize, search, parsedFilters, joinOperator, sortForSWR] + ) + + // SWR Infinite 사용 + const { + data: swrData, + error: swrError, + isLoading: swrIsLoading, + isValidating: swrIsValidating, + mutate: swrMutate, + size: swrSize, + setSize: swrSetSize, + } = useSWRInfinite<InfiniteScrollResponse<TData>>( + getKey, + async (url: string) => { + const response = await fetch(url) + if (!response.ok) throw new Error(`HTTP ${response.status}`) + return response.json() + }, + { + revalidateFirstPage: false, + revalidateOnFocus: false, + revalidateOnReconnect: true, + } + ) + + // 무한 스크롤 데이터 병합 + const infiniteData = React.useMemo(() => { + if (!isInfiniteMode || !swrData) return [] + return swrData.flatMap(page => page.data) + }, [swrData, isInfiniteMode]) + + // 무한 스크롤 메타 정보 + const infiniteMeta = React.useMemo(() => { + if (!isInfiniteMode || !infiniteScrollConfig || !swrData) return null + + const totalCount = swrData[0]?.total ?? null + const hasNextPage = swrData[swrData.length - 1]?.hasNextPage ?? false + const isLoadingMore = swrIsValidating && swrData && typeof swrData[swrSize - 1] !== "undefined" + + return { + enabled: true, + totalCount, + hasNextPage, + isLoadingMore, + loadMore: () => { + if (hasNextPage && !isLoadingMore) { + swrSetSize(prev => prev + 1) + } + }, + reset: () => { + swrSetSize(1) + swrMutate() + }, + refresh: () => swrMutate(), + error: swrError, + isLoading: swrIsLoading, + isEmpty: swrData?.[0]?.data.length === 0, + } + }, [isInfiniteMode, infiniteScrollConfig, swrData, swrIsValidating, swrSize, swrError, swrIsLoading, swrSetSize, swrMutate]) + + // 검색어나 필터 변경 시 무한 스크롤 리셋 + React.useEffect(() => { + if (isInfiniteMode && infiniteMeta) { + infiniteMeta.reset() + } + }, [search, parsedFilters, joinOperator, sortForSWR, isInfiniteMode]) + + // 최종 데이터 결정 + const finalData = isInfiniteMode ? infiniteData : initialData + + // pageSize 변경 핸들러 + const handlePageSizeChange = React.useCallback((newPageSize: number) => { + // URL 상태 업데이트 (이게 핵심!) + void setPerPage(newPageSize) + + // 모드 전환 시 첫 페이지로 리셋 + const wasInfiniteMode = perPage >= INFINITE_SCROLL_THRESHOLD + const willBeInfiniteMode = newPageSize >= INFINITE_SCROLL_THRESHOLD + + if (wasInfiniteMode !== willBeInfiniteMode) { + void setPage(1) + + // 무한 스크롤에서 페이지네이션으로 전환 시 무한 스크롤 데이터 리셋 + if (wasInfiniteMode && infiniteMeta) { + infiniteMeta.reset() + } + } + }, [setPerPage, setPage, perPage, infiniteMeta]) + + // 그리고 onPaginationChange 함수도 수정 + function onPaginationChange(updaterOrValue: Updater<PaginationState>) { + if (isInfiniteMode) return // 무한 스크롤 모드에서는 페이지네이션 변경 무시 + + if (typeof updaterOrValue === "function") { + const newPagination = updaterOrValue(pagination) + void setPage(newPagination.pageIndex + 1) + // perPage 변경은 handlePageSizeChange를 통해서만! + if (newPagination.pageSize !== perPage) { + handlePageSizeChange(newPagination.pageSize) + } + } else { + void setPage(updaterOrValue.pageIndex + 1) + // perPage 변경은 handlePageSizeChange를 통해서만! + if (updaterOrValue.pageSize !== perPage) { + handlePageSizeChange(updaterOrValue.pageSize) + } + } + } + // -------- 나머지 기존 로직들 -------- function onSortingChange(updaterOrValue: Updater<SortingState>) { if (typeof updaterOrValue === "function") { const newSorting = updaterOrValue(sorting) as ExtendedSortingState<TData> @@ -143,13 +360,6 @@ export function useDataTable<TData>({ } } - // -------- Grouping (group) URL 동기화 (멀티 컬럼) -------- - const [grouping, setGrouping] = useQueryState( - "group", - parseAsArrayOf(parseAsString, ",") - .withOptions(queryStateOptions) - .withDefault(initialState?.grouping ?? []) - ) function onGroupingChange(updaterOrValue: Updater<string[]>) { if (typeof updaterOrValue === "function") { const newGrouping = updaterOrValue(grouping) @@ -159,25 +369,26 @@ export function useDataTable<TData>({ } } - // -------- Group Expand/Collapse -------- - const [expanded, setExpanded] = React.useState<ExpandedState>({}) // or true/false - function onExpandedChange(updater: Updater<ExpandedState>) { setExpanded((old) => (typeof updater === "function" ? updater(old) : updater)) } + + const pagination: PaginationState = { + pageIndex: page - 1, + pageSize: perPage, + } - // -------- Filters (search/faceted) URL 동기화 -------- + + // 기존 필터 로직들... (동일) const filterParsers = React.useMemo(() => { return filterFields.reduce< Record<string, Parser<string> | Parser<string[]>> >((acc, field) => { if (field.options) { - // Faceted filter -> 여러 값 가능 acc[field.id] = parseAsArrayOf(parseAsString, ",").withOptions( queryStateOptions ) } else { - // Search filter -> 단일 값 acc[field.id] = parseAsString.withOptions(queryStateOptions) } return acc @@ -190,25 +401,7 @@ export function useDataTable<TData>({ debounceMs ) - // -------- PaginationState 객체 -------- - const pagination: PaginationState = { - pageIndex: page - 1, - pageSize: perPage, - } - function onPaginationChange(updaterOrValue: Updater<PaginationState>) { - if (typeof updaterOrValue === "function") { - const newPagination = updaterOrValue(pagination) - void setPage(newPagination.pageIndex + 1) - void setPerPage(newPagination.pageSize) - } else { - void setPage(updaterOrValue.pageIndex + 1) - void setPerPage(updaterOrValue.pageSize) - } - } - - // -------- ColumnFiltersState -------- const initialColumnFilters: ColumnFiltersState = React.useMemo(() => { - // AdvancedFilter 모드가 아니면 URL에서 온 filterValues를 그대로 적용 return enableAdvancedFilter ? [] : Object.entries(filterValues).reduce<ColumnFiltersState>( @@ -228,7 +421,6 @@ export function useDataTable<TData>({ const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(initialColumnFilters) - // 검색용 / Facet 필터용 컬럼 구분 const { searchableColumns, filterableColumns } = React.useMemo(() => { return enableAdvancedFilter ? { searchableColumns: [], filterableColumns: [] } @@ -258,14 +450,12 @@ export function useDataTable<TData>({ return acc }, {}) - // 빠진 필터는 null로 설정 prev.forEach((prevFilter) => { if (!next.some((filter) => filter.id === prevFilter.id)) { filterUpdates[prevFilter.id] = null } }) - // 필터가 바뀌면 첫 페이지로 void setPage(1) debouncedSetFilterValues(filterUpdates) return next @@ -283,8 +473,9 @@ export function useDataTable<TData>({ // -------- TanStack Table 인스턴스 생성 -------- const table = useReactTable({ ...props, + data: finalData, initialState, - pageCount, + pageCount: isInfiniteMode ? -1 : pageCount, state: { pagination, sorting, @@ -295,7 +486,6 @@ export function useDataTable<TData>({ expanded, }, - // 콜백들 onRowSelectionChange: setRowSelection, onPaginationChange, onSortingChange, @@ -304,31 +494,38 @@ export function useDataTable<TData>({ onGroupingChange, onExpandedChange, - // 기본 모델 getCoreRowModel: getCoreRowModel(), - // 필터 (Advanced 모드 아니면 사용) getFilteredRowModel: enableAdvancedFilter ? undefined : getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), + getPaginationRowModel: isInfiniteMode ? undefined : getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), - // 그룹 + 확장 getGroupedRowModel: getGroupedRowModel(), getExpandedRowModel: getExpandedRowModel(), - // Faceted (Facet 필터용) getFacetedRowModel: enableAdvancedFilter ? undefined : getFacetedRowModel(), - getFacetedUniqueValues: enableAdvancedFilter - ? undefined - : getFacetedUniqueValues(), + getFacetedUniqueValues: enableAdvancedFilter ? undefined : getFacetedUniqueValues(), - // 서버 사이드 사용 manualPagination: true, manualSorting: true, manualFiltering: true, - // 그룹핑도 서버에서 처리한다면 별도 로직이 필요하지만, - // TanStack Table v8에는 manualGrouping 옵션은 없음 - // (그룹핑을 서버에서 이미 해서 내려받는다면 row 구조 처리 필요) - }) - return { table } + return { + table, + // 무한 스크롤 정보 + infiniteScroll: infiniteMeta, + // 모드 정보 + isInfiniteMode, + effectivePageSize, + // 페이지 크기 변경 핸들러 + handlePageSizeChange, + // URL 상태 관리 함수들 + urlState: { + search, + setSearch, + filters: parsedFilters, + setFilters: (newFilters: any[]) => setFilters(JSON.stringify(newFilters)), + joinOperator, + setJoinOperator, + } + } }
\ No newline at end of file |
