diff options
Diffstat (limited to 'components/data-table/data-table-pagination.tsx')
| -rw-r--r-- | components/data-table/data-table-pagination.tsx | 219 |
1 files changed, 149 insertions, 70 deletions
diff --git a/components/data-table/data-table-pagination.tsx b/components/data-table/data-table-pagination.tsx index 4ed63a1b..922dacf1 100644 --- a/components/data-table/data-table-pagination.tsx +++ b/components/data-table/data-table-pagination.tsx @@ -7,6 +7,7 @@ import { ChevronRight, ChevronsLeft, ChevronsRight, + Infinity, } from "lucide-react" import { Button } from "@/components/ui/button" @@ -21,57 +22,99 @@ import { interface DataTablePaginationProps<TData> { table: Table<TData> pageSizeOptions?: Array<number | "All"> + // 무한 스크롤 관련 props + infiniteScroll?: { + enabled: boolean + hasNextPage: boolean + isLoadingMore: boolean + totalCount?: number | null + onLoadMore?: () => void + } + // 페이지 크기 변경 콜백 (필수!) + onPageSizeChange?: (pageSize: number) => void } export function DataTablePagination<TData>({ table, pageSizeOptions = [10, 20, 30, 40, 50, "All"], + infiniteScroll, + onPageSizeChange, }: DataTablePaginationProps<TData>) { // 현재 테이블 pageSize const currentPageSize = table.getState().pagination.pageSize + const isInfiniteMode = infiniteScroll?.enabled || currentPageSize >= 1_000_000 - // "All"을 1,000,000으로 처리할 것이므로, - // 만약 현재 pageSize가 1,000,000이면 화면상 "All"로 표시 - const selectValue = - currentPageSize === 1_000_000 - ? "All" - : String(currentPageSize) + // "All"을 1,000,000으로 처리하고, 무한 스크롤 모드 표시 + const selectValue = isInfiniteMode ? "All" : String(currentPageSize) + + const handlePageSizeChange = (value: string) => { + if (!onPageSizeChange) { + console.warn('DataTablePagination: onPageSizeChange prop is required for page size changes to work') + return + } + + if (value === "All") { + // "All" 선택 시 무한 스크롤 모드로 전환 + onPageSizeChange(1_000_000) // URL 상태 업데이트만 수행 + } else { + const newSize = Number(value) + onPageSizeChange(newSize) // URL 상태 업데이트만 수행 + } + + // table.setPageSize()는 호출하지 않음! + // URL 상태 변경이 테이블 상태로 자동 반영됨 + } return ( <div className="flex w-full flex-col-reverse items-center justify-between gap-4 overflow-auto p-1 sm:flex-row sm:gap-8"> + {/* 선택된 행 및 총 개수 정보 */} <div className="flex-1 whitespace-nowrap text-sm text-muted-foreground"> {table.getFilteredSelectedRowModel().rows.length} of{" "} - {table.getFilteredRowModel().rows.length} row(s) selected. - <span className="ml-4">Total: {table.getRowCount()} records</span> + {isInfiniteMode ? ( + // 무한 스크롤 모드일 때 + <> + {table.getRowModel().rows.length} row(s) selected. + {infiniteScroll?.totalCount !== null && ( + <span className="ml-4"> + Total: {infiniteScroll.totalCount?.toLocaleString()} records + <span className="ml-2 text-xs"> + ({table.getRowModel().rows.length.toLocaleString()} loaded) + </span> + </span> + )} + </> + ) : ( + // 페이지네이션 모드일 때 + <> + {table.getFilteredRowModel().rows.length} row(s) selected. + <span className="ml-4">Total: {table.getRowCount()} records</span> + </> + )} </div> + <div className="flex flex-col-reverse items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8"> {/* Rows per page Select */} <div className="flex items-center space-x-2"> - <p className="whitespace-nowrap text-sm font-medium">Rows per page</p> - <Select - value={selectValue} - onValueChange={(value) => { - if (value === "All") { - // "All"을 1,000,000으로 치환 - table.setPageSize(1_000_000) - } else { - table.setPageSize(Number(value)) - } - }} - > + <p className="whitespace-nowrap text-sm font-medium"> + {isInfiniteMode ? "View mode" : "Rows per page"} + </p> + <Select value={selectValue} onValueChange={handlePageSizeChange}> <SelectTrigger className="h-8 w-[4.5rem]"> <SelectValue placeholder={selectValue} /> </SelectTrigger> <SelectContent side="top"> {pageSizeOptions.map((option) => { - // 화면에 표시할 라벨 const label = option === "All" ? "All" : String(option) - // value도 문자열화 const val = option === "All" ? "All" : String(option) return ( <SelectItem key={val} value={val}> - {label} + <div className="flex items-center space-x-2"> + {option === "All" && ( + <Infinity className="h-3 w-3 text-muted-foreground" /> + )} + <span>{label}</span> + </div> </SelectItem> ) })} @@ -79,54 +122,90 @@ export function DataTablePagination<TData>({ </Select> </div> - {/* 현재 페이지 / 전체 페이지 */} - <div className="flex items-center justify-center text-sm font-medium"> - Page {table.getState().pagination.pageIndex + 1} of{" "} - {table.getPageCount()} - </div> + {/* 페이지네이션 모드일 때만 페이지 정보 표시 */} + {!isInfiniteMode && ( + <> + {/* 현재 페이지 / 전체 페이지 */} + <div className="flex items-center justify-center text-sm font-medium"> + Page {table.getState().pagination.pageIndex + 1} of{" "} + {table.getPageCount()} + </div> - {/* 페이지 이동 버튼 */} - <div className="flex items-center space-x-2"> - <Button - aria-label="Go to first page" - variant="outline" - className="hidden size-8 p-0 lg:flex" - onClick={() => table.setPageIndex(0)} - disabled={!table.getCanPreviousPage()} - > - <ChevronsLeft className="size-4" aria-hidden="true" /> - </Button> - <Button - aria-label="Go to previous page" - variant="outline" - size="icon" - className="size-8" - onClick={() => table.previousPage()} - disabled={!table.getCanPreviousPage()} - > - <ChevronLeft className="size-4" aria-hidden="true" /> - </Button> - <Button - aria-label="Go to next page" - variant="outline" - size="icon" - className="size-8" - onClick={() => table.nextPage()} - disabled={!table.getCanNextPage()} - > - <ChevronRight className="size-4" aria-hidden="true" /> - </Button> - <Button - aria-label="Go to last page" - variant="outline" - size="icon" - className="hidden size-8 lg:flex" - onClick={() => table.setPageIndex(table.getPageCount() - 1)} - disabled={!table.getCanNextPage()} - > - <ChevronsRight className="size-4" aria-hidden="true" /> - </Button> - </div> + {/* 페이지 이동 버튼 */} + <div className="flex items-center space-x-2"> + <Button + aria-label="Go to first page" + variant="outline" + className="hidden size-8 p-0 lg:flex" + onClick={() => table.setPageIndex(0)} + disabled={!table.getCanPreviousPage()} + > + <ChevronsLeft className="size-4" aria-hidden="true" /> + </Button> + <Button + aria-label="Go to previous page" + variant="outline" + size="icon" + className="size-8" + onClick={() => table.previousPage()} + disabled={!table.getCanPreviousPage()} + > + <ChevronLeft className="size-4" aria-hidden="true" /> + </Button> + <Button + aria-label="Go to next page" + variant="outline" + size="icon" + className="size-8" + onClick={() => table.nextPage()} + disabled={!table.getCanNextPage()} + > + <ChevronRight className="size-4" aria-hidden="true" /> + </Button> + <Button + aria-label="Go to last page" + variant="outline" + size="icon" + className="hidden size-8 lg:flex" + onClick={() => table.setPageIndex(table.getPageCount() - 1)} + disabled={!table.getCanNextPage()} + > + <ChevronsRight className="size-4" aria-hidden="true" /> + </Button> + </div> + </> + )} + + {/* 무한 스크롤 모드일 때 로드 더 버튼 */} + {isInfiniteMode && infiniteScroll && ( + <div className="flex items-center space-x-2"> + {infiniteScroll.hasNextPage && ( + <Button + variant="outline" + size="sm" + onClick={infiniteScroll.onLoadMore} + disabled={infiniteScroll.isLoadingMore} + > + {infiniteScroll.isLoadingMore ? ( + <> + <div className="mr-2 h-3 w-3 animate-spin rounded-full border-2 border-current border-t-transparent" /> + Loading... + </> + ) : ( + <> + <ChevronRight className="mr-2 h-3 w-3" /> + Load More + </> + )} + </Button> + )} + {!infiniteScroll.hasNextPage && table.getRowModel().rows.length > 0 && ( + <span className="text-xs text-muted-foreground"> + All data loaded + </span> + )} + </div> + )} </div> </div> ) |
