From 37f55540833c2d5894513eca9fc8f7c6233fc2d2 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 29 May 2025 05:17:13 +0000 Subject: (대표님) 0529 14시 16분 변경사항 저장 (Vendor Data, Docu) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hooks/use-data-table copy.ts | 334 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 334 insertions(+) create mode 100644 hooks/use-data-table copy.ts (limited to 'hooks/use-data-table copy.ts') 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 + extends Omit< + TableOptions, + | "state" + | "pageCount" + | "getCoreRowModel" + | "manualFiltering" + | "manualPagination" + | "manualSorting" + | "onGroupingChange" + | "onExpandedChange" + | "getExpandedRowModel" + >, + Required, "pageCount">> { + filterFields?: DataTableFilterField[] + enableAdvancedFilter?: boolean + history?: "push" | "replace" + scroll?: boolean + shallow?: boolean + throttleMs?: number + debounceMs?: number + startTransition?: React.TransitionStartFunction + clearOnDefault?: boolean + initialState?: Omit, "sorting"> & { + sorting?: ExtendedSortingState + /** + * 기본 그룹핑 컬럼 배열 (멀티 그룹핑) + */ + grouping?: string[] + /** + * 그룹 확장/접기 상태 + */ + expanded?: Record + } +} + +export function useDataTable({ + pageCount = -1, + filterFields = [], + enableAdvancedFilter = false, + history = "replace", + scroll = false, + shallow = true, + throttleMs = 50, + debounceMs = 300, + clearOnDefault = false, + startTransition, + initialState, + ...props +}: UseDataTableProps) { + // 공통 URL QueryState 옵션 + const queryStateOptions = React.useMemo< + Omit, "parse"> + >(() => { + return { + history, + scroll, + shallow, + throttleMs, + debounceMs, + clearOnDefault, + startTransition, + } + }, [ + history, + scroll, + shallow, + throttleMs, + debounceMs, + clearOnDefault, + startTransition, + ]) + + // -------- RowSelection & ColumnVisibility는 URL 동기화 없이 로컬 상태 ---------- + const [rowSelection, setRowSelection] = React.useState( + initialState?.rowSelection ?? {} + ) + const [columnVisibility, setColumnVisibility] = + React.useState(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() + .withOptions(queryStateOptions) + .withDefault(initialState?.sorting ?? []) + ) + function onSortingChange(updaterOrValue: Updater) { + if (typeof updaterOrValue === "function") { + const newSorting = updaterOrValue(sorting) as ExtendedSortingState + void setSorting(newSorting) + } else { + void setSorting(updaterOrValue as ExtendedSortingState) + } + } + + // -------- Grouping (group) URL 동기화 (멀티 컬럼) -------- + const [grouping, setGrouping] = useQueryState( + "group", + parseAsArrayOf(parseAsString, ",") + .withOptions(queryStateOptions) + .withDefault(initialState?.grouping ?? []) + ) + function onGroupingChange(updaterOrValue: Updater) { + if (typeof updaterOrValue === "function") { + const newGrouping = updaterOrValue(grouping) + void setGrouping(newGrouping) + } else { + void setGrouping(updaterOrValue) + } + } + + // -------- Group Expand/Collapse -------- + const [expanded, setExpanded] = React.useState({}) // or true/false + + function onExpandedChange(updater: Updater) { + setExpanded((old) => (typeof updater === "function" ? updater(old) : updater)) + } + + // -------- Filters (search/faceted) URL 동기화 -------- + const filterParsers = React.useMemo(() => { + return filterFields.reduce< + Record | Parser> + >((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) { + 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( + (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(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) => { + if (enableAdvancedFilter) return + setColumnFilters((prev) => { + const next = + typeof updaterOrValue === "function" + ? updaterOrValue(prev) + : updaterOrValue + + const filterUpdates = next.reduce< + Record + >((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 -- cgit v1.2.3