summaryrefslogtreecommitdiff
path: root/hooks
diff options
context:
space:
mode:
Diffstat (limited to 'hooks')
-rw-r--r--hooks/use-data-table copy.ts334
-rw-r--r--hooks/use-data-table.ts315
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