"use client" import * as React from "react" import type { ExtendedColumnSort, ExtendedSortingState, StringKeyOf, } from "@/types/table" import type { SortDirection, Table } from "@tanstack/react-table" import { ArrowDownUp, Check, ChevronsUpDown, GripVertical, Trash2, } from "lucide-react" import { useQueryState } from "nuqs" import { dataTableConfig } from "@/config/data-table" import { getSortingStateParser } from "@/lib/parsers" import { cn, toSentenceCase } from "@/lib/utils" import { useDebouncedCallback } from "@/hooks/use-debounced-callback" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Sortable, SortableDragHandle, SortableItem, } from "@/components/ui/sortable" import { useTranslation } from '@/i18n/client' import { useParams, usePathname } from "next/navigation"; interface DataTableSortListProps { table: Table debounceMs: number shallow?: boolean } let renderCount = 0; export function DataTableSortList({ table, debounceMs, shallow, }: DataTableSortListProps) { renderCount++; const params = useParams(); const lng = params?.lng as string; const { t } = useTranslation(lng); const id = React.useId() const initialSorting = (table.initialState.sorting ?? []) as ExtendedSortingState // ✅ 파서를 안정화 - 한 번만 생성되도록 수정 const sortingParser = React.useMemo(() => { // 첫 번째 행의 데이터를 안정적으로 가져오기 const sampleData = table.getRowModel().rows[0]?.original; return getSortingStateParser(sampleData); }, []); // ✅ 빈 dependency - 한 번만 생성 const [sorting, setSorting] = useQueryState( "sort", sortingParser .withDefault(initialSorting) .withOptions({ clearOnDefault: true, shallow, }) ) // ✅ debouncedSetSorting - 컴포넌트 최상위로 이동 const debouncedSetSorting = useDebouncedCallback(setSorting, debounceMs); // ✅ uniqueSorting 메모이제이션 const uniqueSorting = React.useMemo( () => sorting.filter( (sort, index, self) => index === self.findIndex((t) => t.id === sort.id) ), [sorting] ) // ✅ sortableColumns 메모이제이션 const sortableColumns = React.useMemo( () => table .getAllColumns() .flatMap((column) => { // 그룹 컬럼이면 leaf 컬럼만 추출 if (column.columns && column.columns.length > 0) { return column.columns.filter(c => c.getCanSort()); } return column.getCanSort() ? [column] : []; }) .filter( (column) => column.getCanSort() && !sorting.some((s) => s.id === column.id) ) .map((column) => ({ id: column.id, label: toSentenceCase(column.id), selected: false, })), [sorting, table] ) // ✅ 함수들을 useCallback으로 메모이제이션 const addSort = React.useCallback(() => { const firstAvailableColumn = sortableColumns.find( (column) => !sorting.some((s) => s.id === column.id) ) if (!firstAvailableColumn) return void setSorting([ ...sorting, { id: firstAvailableColumn.id as StringKeyOf, desc: false, }, ]) }, [sortableColumns, sorting, setSorting]); const updateSort = React.useCallback(({ id, field, debounced = false, }: { id: string field: Partial> debounced?: boolean }) => { const updateFunction = debounced ? debouncedSetSorting : setSorting updateFunction((prevSorting) => { if (!prevSorting) return prevSorting const updatedSorting = prevSorting.map((sort) => sort.id === id ? { ...sort, ...field } : sort ) return updatedSorting }) }, [debouncedSetSorting, setSorting]); const removeSort = React.useCallback((id: string) => { void setSorting((prevSorting) => prevSorting.filter((item) => item.id !== id) ) }, [setSorting]); const resetSorting = React.useCallback(() => { setSorting(null); }, [setSorting]); return (
} > 0 ? "gap-3.5" : "gap-2" )} > {uniqueSorting.length > 0 ? (

Sort by

) : (

No sorting applied

Add sorting to organize your results.

)}
{uniqueSorting.map((sort) => { const sortId = `${id}-sort-${sort.id}` const fieldListboxId = `${sortId}-field-listbox` const fieldTriggerId = `${sortId}-field-trigger` const directionListboxId = `${sortId}-direction-listbox` return (
document.getElementById(fieldTriggerId)?.focus() } > No fields found. {sortableColumns.map((column) => ( { const newFieldTriggerId = `${id}-sort-${value}-field-trigger` updateSort({ id: sort.id, field: { id: value as StringKeyOf, }, }) requestAnimationFrame(() => { document .getElementById(newFieldTriggerId) ?.focus() }) }} > {column.label} ))}
) })}
{sorting.length > 0 ? ( ) : null}
) }