"use client" import * as React from "react" import { flexRender, type Table as TanstackTable } from "@tanstack/react-table" import { ChevronRight, ChevronUp, Loader2 } from "lucide-react" import { useIntersection } from "@mantine/hooks" import { cn } from "@/lib/utils" import { getCommonPinningStyles } from "@/lib/data-table" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { Button } from "@/components/ui/button" import { DataTableResizer } from "@/components/data-table/data-table-resizer" import { useAutoSizeColumns } from "@/hooks/useAutoSizeColumns" interface InfiniteDataTableProps extends React.HTMLAttributes { table: TanstackTable floatingBar?: React.ReactNode | null autoSizeColumns?: boolean compact?: boolean // 무한 스크롤 관련 props hasNextPage?: boolean isLoadingMore?: boolean onLoadMore?: () => void totalCount?: number | null isEmpty?: boolean } /** * 무한 스크롤 지원 DataTable */ export function InfiniteDataTable({ table, floatingBar = null, autoSizeColumns = true, compact = false, hasNextPage = false, isLoadingMore = false, onLoadMore, totalCount = null, isEmpty = false, children, className, maxHeight, ...props }: InfiniteDataTableProps & { maxHeight?: string | number }) { useAutoSizeColumns(table, autoSizeColumns) // Intersection Observer for infinite scroll const { ref: loadMoreRef, entry } = useIntersection({ threshold: 0.1, }) // 자동 로딩 트리거 React.useEffect(() => { if (entry?.isIntersecting && hasNextPage && !isLoadingMore && onLoadMore) { onLoadMore() } }, [entry?.isIntersecting, hasNextPage, isLoadingMore, onLoadMore]) // 컴팩트 모드를 위한 클래스 정의 const compactStyles = compact ? { row: "h-7", cell: "py-1 px-2 text-sm", groupRow: "py-1 bg-muted/20 text-sm", emptyRow: "h-16", } : { row: "", cell: "", groupRow: "bg-muted/20", emptyRow: "h-24", } return (
{children} {/* 총 개수 표시 */} {totalCount !== null && (
총 {totalCount.toLocaleString()}개 항목 {table.getRowModel().rows.length > 0 && ( (현재 {table.getRowModel().rows.length.toLocaleString()}개 로드됨) )}
)}
{/* 테이블 헤더 */} {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) => { // 그룹핑 헤더 Row if (row.getIsGrouped()) { const groupingColumnId = row.groupingColumnId ?? "" const groupingColumn = table.getColumn(groupingColumnId) let columnLabel = groupingColumnId if (groupingColumn) { const headerDef = groupingColumn.columnDef.meta?.excelHeader if (typeof headerDef === "string") { columnLabel = headerDef } } return ( {row.getCanExpand() && ( )} {columnLabel}: {row.getValue(groupingColumnId)} ({row.subRows.length} rows) ) } // 일반 Row return ( {row.getVisibleCells().map((cell) => { if (cell.column.getIsGrouped()) { return null } return ( {flexRender( cell.column.columnDef.cell, cell.getContext() )} ) })} ) })} ) : isEmpty ? ( // 데이터가 없을 때 No results. ) : null}
{/* 무한 스크롤 로딩 영역 */}
{hasNextPage && ( <> {/* Intersection Observer 타겟 */}
{isLoadingMore && (
로딩 중...
)} {/* 수동 로드 버튼 (자동 로딩 실패 시 대안) */} {!isLoadingMore && onLoadMore && ( )} )} {!hasNextPage && table.getRowModel().rows.length > 0 && (

모든 데이터를 불러왔습니다.

)}
{/* 선택된 행 정보 */} {table.getFilteredSelectedRowModel().rows.length > 0 && (
{table.getFilteredSelectedRowModel().rows.length} of{" "} {table.getRowModel().rows.length} row(s) selected.
)} {/* Floating Bar (선택된 행 있을 때) */} {table.getFilteredSelectedRowModel().rows.length > 0 && floatingBar}
) }