diff options
Diffstat (limited to 'components/data-table')
| -rw-r--r-- | components/data-table/data-table-filter-list.tsx | 44 | ||||
| -rw-r--r-- | components/data-table/data-table-grobal-filter.tsx | 4 | ||||
| -rw-r--r-- | components/data-table/data-table-view-options.tsx | 12 | ||||
| -rw-r--r-- | components/data-table/data-table.tsx | 7 |
4 files changed, 52 insertions, 15 deletions
diff --git a/components/data-table/data-table-filter-list.tsx b/components/data-table/data-table-filter-list.tsx index 6088e912..ea4b1f90 100644 --- a/components/data-table/data-table-filter-list.tsx +++ b/components/data-table/data-table-filter-list.tsx @@ -66,6 +66,7 @@ import { } from "@/components/ui/sortable" import { useParams } from 'next/navigation'; import { useTranslation } from '@/i18n/client' +import deepEqual from "fast-deep-equal" interface DataTableFilterListProps<TData> { table: Table<TData> @@ -78,6 +79,10 @@ interface DataTableFilterListProps<TData> { onFiltersChange?: (filters: Filter<TData>[], joinOperator: JoinOperator) => void } +export function isSame(a: unknown, b: unknown) { + return JSON.stringify(a) === JSON.stringify(b) +} + export function DataTableFilterList<TData>({ table, filterFields, @@ -88,6 +93,11 @@ export function DataTableFilterList<TData>({ onFiltersChange, }: DataTableFilterListProps<TData>) { + const prevRef = React.useRef<{ + filters: Filter<TData>[] + join: JoinOperator + } | null>(null) + const params = useParams(); const lng = params ? (params.lng as string) : 'en'; @@ -114,13 +124,25 @@ export function DataTableFilterList<TData>({ }) ) + const safeSetFilters = React.useCallback( + (next: Filter<TData>[] | ((p: Filter<TData>[]) => Filter<TData>[])) => { + setFilters((prev) => { + const value = typeof next === "function" ? next(prev) : next + return deepEqual(prev, value) ? prev : value // <─ 달라진 게 없으면 그대로 + }) + }, + [setFilters] + ) + + + // ✅ 외부 필터가 전달되면 URL 상태를 업데이트 React.useEffect(() => { - if (externalFilters && externalFilters.length > 0) { + if (externalFilters && !deepEqual(externalFilters, filters)) { console.log("=== 외부 필터 적용 ===", externalFilters); - setFilters(externalFilters); + safeSetFilters(externalFilters); } - }, [externalFilters, setFilters]); + }, [externalFilters, setFilters, safeSetFilters]); React.useEffect(() => { if (externalJoinOperator) { @@ -130,12 +152,20 @@ export function DataTableFilterList<TData>({ }, [externalJoinOperator, setJoinOperator]); // ✅ 필터 변경 시 부모에게 알림 + React.useEffect(() => { - if (onFiltersChange) { - onFiltersChange(filters, joinOperator); + const prev = prevRef.current + const changed = + !prev || + !deepEqual(prev.filters, filters) || + prev.join !== joinOperator + + if (changed) { + prevRef.current = { filters, join: joinOperator } + onFiltersChange?.(filters, joinOperator) } - }, [filters, joinOperator, onFiltersChange]); - + }, [filters, joinOperator, onFiltersChange]) + const debouncedSetFilters = useDebouncedCallback(setFilters, debounceMs) function addFilter() { diff --git a/components/data-table/data-table-grobal-filter.tsx b/components/data-table/data-table-grobal-filter.tsx index a1f0a6f3..ca60bf02 100644 --- a/components/data-table/data-table-grobal-filter.tsx +++ b/components/data-table/data-table-grobal-filter.tsx @@ -24,8 +24,8 @@ export function DataTableGlobalFilter() { // Debounced callback that sets the URL param after `delay` ms const debouncedSetSearch = useDebouncedCallback((value: string) => { - setSearchValue(value) - }, 300) // 300ms or chosen delay + if (value !== searchValue) setSearchValue(value.trim() === "" ? undefined : value); + }, 300) // When user types, update local `tempValue` immediately, // then call the debounced function to update the query param diff --git a/components/data-table/data-table-view-options.tsx b/components/data-table/data-table-view-options.tsx index 422e3065..b689adab 100644 --- a/components/data-table/data-table-view-options.tsx +++ b/components/data-table/data-table-view-options.tsx @@ -39,6 +39,7 @@ import { } from "@/components/ui/sortable" import { useTranslation } from '@/i18n/client' import { useParams, usePathname } from "next/navigation"; +import deepEqual from "fast-deep-equal" /** @@ -70,6 +71,7 @@ export function DataTableViewOptions<TData>({ }: DataTableViewOptionsProps<TData>) { const triggerRef = React.useRef<HTMLButtonElement>(null) + const params = useParams(); const lng = params?.lng as string; const { t } = useTranslation(lng); @@ -115,11 +117,11 @@ export function DataTableViewOptions<TData>({ const finalOrder = [...nonHideable, ...columnOrder] // Now we set the table's official column order - table.setColumnOrder(finalOrder) - - // Reset auto-size when column order changes - resetAutoSize?.() - }, [columnOrder, hideableCols, table, resetAutoSize]) + if (!deepEqual(table.getState().columnOrder, finalOrder)) { + table.setColumnOrder(finalOrder) + resetAutoSize?.() + } + }, [columnOrder, hideableCols.join("|"), table, resetAutoSize]) return ( diff --git a/components/data-table/data-table.tsx b/components/data-table/data-table.tsx index 33fca5b8..b898c2ea 100644 --- a/components/data-table/data-table.tsx +++ b/components/data-table/data-table.tsx @@ -66,9 +66,14 @@ export function DataTable<TData>({ [compact] ); + const stableChildren = React.useMemo(() => { + console.log("📦 DataTable children 메모이제이션됨"); + return children; + }, [children]); + return ( <div className={cn("w-full space-y-2.5 overflow-auto", className)} {...props}> - {children} + {stableChildren} <div className="max-w-[100vw] overflow-auto" style={{ maxHeight: maxHeight || '35rem' }} > <Table className="[&>thead]:sticky [&>thead]:top-0 [&>thead]:z-10 table-fixed"> {/* 테이블 헤더 */} |
