From e0dfb55c5457aec489fc084c4567e791b4c65eb1 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 26 Mar 2025 00:37:41 +0000 Subject: 3/25 까지의 대표님 작업사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/client-data-table/data-table.tsx | 336 ++++++++++++++++++++++++++++ 1 file changed, 336 insertions(+) create mode 100644 components/client-data-table/data-table.tsx (limited to 'components/client-data-table/data-table.tsx') diff --git a/components/client-data-table/data-table.tsx b/components/client-data-table/data-table.tsx new file mode 100644 index 00000000..ff10bfe4 --- /dev/null +++ b/components/client-data-table/data-table.tsx @@ -0,0 +1,336 @@ +"use client" + +import * as React from "react" +import { + ColumnDef, + ColumnFiltersState, + SortingState, + VisibilityState, + flexRender, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, + Table, + getGroupedRowModel, + getExpandedRowModel, + ColumnSizingState, ColumnPinningState +} from "@tanstack/react-table" +import { + Table as UiTable, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { getCommonPinningStyles } from "@/lib/data-table" +import { ChevronRight, ChevronUp } from "lucide-react" + +import { ClientDataTableAdvancedToolbar } from "./data-table-toolbar" +import { ClientDataTablePagination } from "./data-table-pagination" +import { DataTableResizer } from "./data-table-resizer" +import { useAutoSizeColumns } from "@/hooks/useAutoSizeColumns" + +interface DataTableProps { + columns: ColumnDef[] + data: TData[] + advancedFilterFields: any[] + autoSizeColumns?: boolean + onSelectedRowsChange?: (selected: TData[]) => void + + /** 추가로 표시할 버튼/컴포넌트 */ + children?: React.ReactNode +} + +export function ClientDataTable({ + columns, + data, + advancedFilterFields, + autoSizeColumns = true, + children, + onSelectedRowsChange +}: DataTableProps) { + + + // (1) React Table 상태 + const [rowSelection, setRowSelection] = React.useState({}) + const [columnVisibility, setColumnVisibility] = React.useState({}) + const [columnFilters, setColumnFilters] = React.useState([]) + const [sorting, setSorting] = React.useState([]) + const [grouping, setGrouping] = React.useState([]) + const [columnSizing, setColumnSizing] = React.useState({}) + + // 실제 리사이징 상태만 추적 + const [isResizing, setIsResizing] = React.useState(false) + + // 리사이징 상태를 추적하기 위한 ref + const isResizingRef = React.useRef(false) + + // 리사이징 이벤트 핸들러 + const handleResizeStart = React.useCallback(() => { + isResizingRef.current = true + setIsResizing(true) + }, []) + + const handleResizeEnd = React.useCallback(() => { + isResizingRef.current = false + setIsResizing(false) + }, []) + + const [columnPinning, setColumnPinning] = React.useState({ + left: [], + right: ["update"], + }) + + const table = useReactTable({ + data, + columns, + state: { + sorting, + columnVisibility, + rowSelection, + columnFilters, + grouping, + columnSizing, + columnPinning + }, + columnResizeMode: "onChange", + onColumnSizingChange: setColumnSizing, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + onGroupingChange: setGrouping, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + getGroupedRowModel: getGroupedRowModel(), + autoResetPageIndex: false, + getExpandedRowModel: getExpandedRowModel(), + enableColumnPinning:true, + onColumnPinningChange:setColumnPinning + + }) + + useAutoSizeColumns(table, autoSizeColumns) + + // 컴포넌트 마운트 시 강제로 리사이징 상태 초기화 + React.useEffect(() => { + // 강제로 초기 상태는 리사이징 비활성화 + setIsResizing(false) + isResizingRef.current = false + + // 전역 마우스 이벤트 핸들러 + const handleMouseUp = () => { + if (isResizingRef.current) { + handleResizeEnd() + } + } + + // 이벤트 리스너 등록 + window.addEventListener('mouseup', handleMouseUp) + window.addEventListener('touchend', handleMouseUp) + + return () => { + // 이벤트 리스너 정리 + window.removeEventListener('mouseup', handleMouseUp) + window.removeEventListener('touchend', handleMouseUp) + + // 컴포넌트 언마운트 시 정리 + setIsResizing(false) + isResizingRef.current = false + } + }, [handleResizeEnd]) + + React.useEffect(() => { + if (!onSelectedRowsChange) return + const selectedRows = table + .getSelectedRowModel() + .flatRows.map((row) => row.original) + onSelectedRowsChange(selectedRows) + }, [rowSelection, table, onSelectedRowsChange]) + + // (2) 렌더 + return ( +
+ {/* 툴바에 children을 넘기기 */} + + {children} + + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + // 만약 이 컬럼이 현재 "그룹핑" 상태라면 헤더도 표시하지 않음 + if (header.column.getIsGrouped()) { + return null + } + + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + {/* 리사이즈 핸들 - 헤더에만 추가 */} + {header.column.getCanResize() && ( + + )} + + ) + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => { + // --------------------------------------------------- + // 1) "그룹핑 헤더" Row인지 확인 + // --------------------------------------------------- + if (row.getIsGrouped()) { + // row.groupingColumnId로 어떤 컬럼을 기준으로 그룹화 되었는지 알 수 있음 + const groupingColumnId = row.groupingColumnId ?? "" + const groupingColumn = table.getColumn(groupingColumnId) // 해당 column 객체 + + // 컬럼 라벨 가져오기 + let columnLabel = groupingColumnId + if (groupingColumn) { + const headerDef = groupingColumn.columnDef.meta?.excelHeader + if (typeof headerDef === "string") { + columnLabel = headerDef + } + } + + return ( + + {/* 그룹 헤더는 한 줄에 합쳐서 보여주고, 토글 버튼 + 그룹 라벨 + 값 표기 */} + + {/* 확장/축소 버튼 (아이콘 중앙 정렬 + Indent) */} + {row.getCanExpand() && ( + + )} + + {/* Group Label + 값 */} + + {columnLabel}: {row.getValue(groupingColumnId)} + + + ({row.subRows.length} rows) + + + + ) + } + + // --------------------------------------------------- + // 2) 일반 Row + // → "그룹핑된 컬럼"은 숨긴다 + // --------------------------------------------------- + return ( + + {row.getVisibleCells().map((cell) => { + // 이 셀의 컬럼이 grouped라면 숨긴다 + if (cell.column.getIsGrouped()) { + return null + } + + return ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() + )} + + ) + })} + + ) + }) + ) : ( + // --------------------------------------------------- + // 3) 데이터가 없을 때 + // --------------------------------------------------- + + + No results. + + + )} + + + + {/* 리사이징 시에만 캡처 레이어 활성화 */} + {isResizing && ( +
+ )} +
+
+ + +
+ ) +} \ No newline at end of file -- cgit v1.2.3