From b8e8328b1ffffb80bf4ebb776a4a24e5680fc5bc Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 28 Mar 2025 00:42:08 +0000 Subject: 테이블 칼럼 리사이즈 및 핀 충돌 해결 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/data-table/data-table-column-header.tsx | 15 +- components/data-table/data-table-pin-right.tsx | 173 +++++++++++++++++---- components/data-table/data-table.tsx | 30 ++-- 3 files changed, 171 insertions(+), 47 deletions(-) (limited to 'components/data-table') diff --git a/components/data-table/data-table-column-header.tsx b/components/data-table/data-table-column-header.tsx index aa0c754b..795531c8 100644 --- a/components/data-table/data-table-column-header.tsx +++ b/components/data-table/data-table-column-header.tsx @@ -24,15 +24,18 @@ export function DataTableColumnHeader({ className, }: DataTableColumnHeaderProps) { if (!column.getCanSort() && !column.getCanHide()) { - return
{title}
+ return
{title}
} const ascValue = `${column.id}-asc` const descValue = `${column.id}-desc` const hideValue = `${column.id}-hide` + // 현재 컬럼 pinned 상태 + const isPinned = column.getIsPinned(); + return ( -
+
) -} +} \ No newline at end of file diff --git a/components/data-table/data-table-pin-right.tsx b/components/data-table/data-table-pin-right.tsx index 051dd985..3ed42402 100644 --- a/components/data-table/data-table-pin-right.tsx +++ b/components/data-table/data-table-pin-right.tsx @@ -1,7 +1,7 @@ "use client" import * as React from "react" -import { type Table } from "@tanstack/react-table" +import { type Column, type Table } from "@tanstack/react-table" import { Check, ChevronsUpDown, MoveRight } from "lucide-react" import { cn, toSentenceCase } from "@/lib/utils" @@ -13,6 +13,7 @@ import { CommandInput, CommandItem, CommandList, + CommandSeparator, } from "@/components/ui/command" import { Popover, @@ -21,12 +22,99 @@ import { } from "@/components/ui/popover" /** - * “Pin Right” Popover. Similar to PinLeftButton, but pins columns to "right". + * Helper function to check if a column is a parent column (has subcolumns) + */ +function isParentColumn(column: Column): boolean { + return column.columns && column.columns.length > 0 +} + +/** + * Helper function to pin all subcolumns of a parent column + */ +function pinSubColumns( + column: Column, + pinType: false | "left" | "right" +): void { + // If this is a parent column, pin all its subcolumns + if (isParentColumn(column)) { + column.columns.forEach((subColumn) => { + // Recursively handle nested columns + pinSubColumns(subColumn, pinType) + }) + } else { + // For leaf columns, apply the pin if possible + if (column.getCanPin?.()) { + column.pin?.(pinType) + } + } +} + +/** + * Checks if all subcolumns of a parent column are pinned to the specified side + */ +function areAllSubColumnsPinned( + column: Column, + pinType: "left" | "right" +): boolean { + if (isParentColumn(column)) { + // Check if all subcolumns are pinned + return column.columns.every((subColumn) => + areAllSubColumnsPinned(subColumn, pinType) + ) + } else { + // For leaf columns, check if it's pinned to the specified side + return column.getIsPinned?.() === pinType + } +} + +/** + * "Pin Right" Popover. Supports pinning both individual columns and header groups. */ export function PinRightButton({ table }: { table: Table }) { const [open, setOpen] = React.useState(false) const triggerRef = React.useRef(null) + // Get all columns that can be pinned, including parent columns + const pinnableColumns = React.useMemo(() => { + return table.getAllColumns().filter((column) => { + // If it's a leaf column, check if it can be pinned + if (!isParentColumn(column)) { + return column.getCanPin?.() + } + + // If it's a parent column, check if at least one subcolumn can be pinned + return column.columns.some((subCol) => { + if (isParentColumn(subCol)) { + // Recursively check nested columns + return subCol.columns.some(c => c.getCanPin?.()) + } + return subCol.getCanPin?.() + }) + }) + }, [table]) + + // Handle column pinning + const handleColumnPin = React.useCallback((column: Column) => { + // For parent columns, pin/unpin all subcolumns + if (isParentColumn(column)) { + const allPinned = areAllSubColumnsPinned(column, "right") + pinSubColumns(column, allPinned ? false : "right") + } else { + // For leaf columns, toggle pin state + const isPinned = column.getIsPinned?.() === "right" + column.pin?.(isPinned ? false : "right") + } + }, []) + + // Check if a column or its subcolumns are pinned right + const isColumnPinned = React.useCallback((column: Column): boolean => { + if (isParentColumn(column)) { + return areAllSubColumnsPinned(column, "right") + } else { + return column.getIsPinned?.() === "right" + } + }, []) + return ( @@ -37,17 +125,17 @@ export function PinRightButton({ table }: { table: Table }) { className="h-8 gap-2" > - + Right - + triggerRef.current?.focus()} > @@ -55,30 +143,57 @@ export function PinRightButton({ table }: { table: Table }) { No columns found. - {table - .getAllLeafColumns() - .filter((col) => col.getCanPin?.()) - .map((column) => { - const pinned = column.getIsPinned?.() - return ( - { - column.pin?.(pinned === "right" ? false : "right") - }} - > - - {toSentenceCase(column.id)} - - - - ) - })} + {/* Header Columns (Parent Columns) */} + {pinnableColumns + .filter(isParentColumn) + .map((column) => ( + { + handleColumnPin(column) + }} + className="font-medium" + > + + {column.id === "Basic Info" || column.id === "Metadata" + ? column.id // Use column ID directly for common groups + : toSentenceCase(column.id)} + + + + ))} + + {pinnableColumns.some(isParentColumn) && + pinnableColumns.some(col => !isParentColumn(col)) && ( + + )} + + {/* Leaf Columns (individual columns) */} + {pinnableColumns + .filter(col => !isParentColumn(col)) + .map((column) => ( + { + handleColumnPin(column) + }} + > + + {toSentenceCase(column.id)} + + + + ))} diff --git a/components/data-table/data-table.tsx b/components/data-table/data-table.tsx index 3d01994a..b1027cc0 100644 --- a/components/data-table/data-table.tsx +++ b/components/data-table/data-table.tsx @@ -41,8 +41,8 @@ export function DataTable({ return (
{children} -
- +
+
{/* ------------------------------- Table Header → 그룹핑된 컬럼의 헤더는 숨김 처리 @@ -60,24 +60,26 @@ export function DataTable({ - {header.isPlaceholder - ? null - : flexRender( +
+ {header.isPlaceholder + ? null + : flexRender( header.column.columnDef.header, header.getContext() )} - - {/* 리사이즈 핸들 - 별도의 컴포넌트로 분리 */} - {header.column.getCanResize() && ( - - )} + + {/* 리사이즈 핸들 - 별도의 컴포넌트로 분리 */} + {header.column.getCanResize() && ( + + )} +
) })} @@ -115,7 +117,7 @@ export function DataTable({ data-state={row.getIsExpanded() && "expanded"} > {/* 그룹 헤더는 한 줄에 합쳐서 보여주고, 토글 버튼 + 그룹 라벨 + 값 표기 */} - + {/* 확장/축소 버튼 (아이콘 중앙 정렬 + Indent) */} {row.getCanExpand() && (