ERP-node/frontend/components/common/Pagination.tsx

227 lines
6.8 KiB
TypeScript
Raw Normal View History

2025-08-21 09:41:46 +09:00
"use client";
import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { cn } from "@/lib/utils";
export interface PaginationInfo {
currentPage: number;
totalPages: number;
totalItems: number;
itemsPerPage: number;
startItem: number;
endItem: number;
}
export interface PaginationProps {
paginationInfo: PaginationInfo;
onPageChange: (page: number) => void;
onPageSizeChange?: (pageSize: number) => void;
showPageSizeSelector?: boolean;
pageSizeOptions?: number[];
className?: string;
}
/**
*
*
* @example
* <Pagination
* paginationInfo={{
* currentPage: 1,
* totalPages: 10,
* totalItems: 200,
* itemsPerPage: 20,
* startItem: 1,
* endItem: 20
* }}
* onPageChange={(page) => console.log('Page changed:', page)}
* onPageSizeChange={(size) => console.log('Page size changed:', size)}
* showPageSizeSelector={true}
* pageSizeOptions={[10, 20, 50, 100]}
* />
*/
export function Pagination({
paginationInfo,
onPageChange,
onPageSizeChange,
showPageSizeSelector = false,
pageSizeOptions = [10, 20, 50, 100],
className,
}: PaginationProps) {
const { currentPage, totalPages, totalItems, itemsPerPage, startItem, endItem } = paginationInfo;
// 페이지 버튼 범위 계산 (현재 페이지 기준으로 앞뒤 2개씩)
const getPageNumbers = () => {
const delta = 2;
const range = [];
const rangeWithDots = [];
// 시작과 끝 계산
let start = Math.max(1, currentPage - delta);
let end = Math.min(totalPages, currentPage + delta);
// 범위 조정
if (end - start < delta * 2) {
if (start === 1) {
end = Math.min(totalPages, start + delta * 2);
} else if (end === totalPages) {
start = Math.max(1, end - delta * 2);
}
}
// 페이지 번호 배열 생성
for (let i = start; i <= end; i++) {
range.push(i);
}
// 첫 페이지와 점 추가
if (start > 1) {
rangeWithDots.push(1);
if (start > 2) {
rangeWithDots.push("...");
}
}
// 중간 범위 추가
rangeWithDots.push(...range);
// 마지막 페이지와 점 추가
if (end < totalPages) {
if (end < totalPages - 1) {
rangeWithDots.push("...");
}
rangeWithDots.push(totalPages);
}
return rangeWithDots;
};
const pageNumbers = getPageNumbers();
// 페이지 변경 핸들러
const handlePageChange = (page: number) => {
if (page >= 1 && page <= totalPages && page !== currentPage) {
onPageChange(page);
}
};
// 페이지 크기 변경 핸들러
const handlePageSizeChange = (newPageSize: string) => {
const size = parseInt(newPageSize, 10);
if (onPageSizeChange && size !== itemsPerPage) {
onPageSizeChange(size);
}
};
// 항상 페이지네이션을 표시 (1페이지일 때도 표시)
2025-08-21 09:41:46 +09:00
return (
2025-08-27 11:33:27 +09:00
<div className={cn("flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between", className)}>
2025-08-21 09:41:46 +09:00
{/* 페이지 정보 */}
2025-08-27 11:33:27 +09:00
<div className="text-muted-foreground text-center text-sm lg:text-left">
2025-08-21 09:41:46 +09:00
<span className="font-medium">{startItem.toLocaleString()}</span>
{" - "}
<span className="font-medium">{endItem.toLocaleString()}</span>
{" / "}
<span className="font-medium">{totalItems.toLocaleString()}</span>
</div>
{/* 페이지네이션 컨트롤 */}
2025-08-27 11:33:27 +09:00
<div className="flex flex-col items-center gap-4 lg:flex-row">
2025-08-21 09:41:46 +09:00
{/* 페이지 크기 선택 */}
{showPageSizeSelector && onPageSizeChange && (
2025-08-27 11:33:27 +09:00
<div className="flex items-center justify-center gap-2">
2025-08-21 09:41:46 +09:00
<span className="text-muted-foreground text-sm"></span>
<Select value={itemsPerPage.toString()} onValueChange={handlePageSizeChange}>
<SelectTrigger className="w-20">
<SelectValue />
</SelectTrigger>
<SelectContent>
{pageSizeOptions.map((size) => (
<SelectItem key={size} value={size.toString()}>
{size}
</SelectItem>
))}
</SelectContent>
</Select>
<span className="text-muted-foreground text-sm"></span>
</div>
)}
{/* 페이지 버튼들 */}
2025-08-27 11:33:27 +09:00
<div className="flex items-center justify-center gap-1">
2025-08-21 09:41:46 +09:00
{/* 첫 페이지로 */}
<Button
2025-08-27 11:33:27 +09:00
variant="ghost"
2025-08-21 09:41:46 +09:00
size="sm"
onClick={() => handlePageChange(1)}
disabled={currentPage === 1}
2025-08-27 11:33:27 +09:00
className="h-8 w-8 p-0 lg:h-9 lg:w-9"
2025-08-21 09:41:46 +09:00
title="첫 페이지"
>
2025-08-27 11:33:27 +09:00
<ChevronsLeft className="h-3 w-3 lg:h-4 lg:w-4" />
2025-08-21 09:41:46 +09:00
</Button>
{/* 이전 페이지 */}
<Button
2025-08-27 11:33:27 +09:00
variant="ghost"
2025-08-21 09:41:46 +09:00
size="sm"
onClick={() => handlePageChange(currentPage - 1)}
disabled={currentPage === 1}
2025-08-27 11:33:27 +09:00
className="h-8 w-8 p-0 lg:h-9 lg:w-9"
2025-08-21 09:41:46 +09:00
title="이전 페이지"
>
2025-08-27 11:33:27 +09:00
<ChevronLeft className="h-3 w-3 lg:h-4 lg:w-4" />
2025-08-21 09:41:46 +09:00
</Button>
{/* 페이지 번호들 */}
{pageNumbers.map((page, index) => (
<div key={index}>
{page === "..." ? (
2025-08-27 11:33:27 +09:00
<span className="text-muted-foreground flex h-8 w-8 items-center justify-center text-sm lg:h-9 lg:w-9">
...
</span>
2025-08-21 09:41:46 +09:00
) : (
<Button
2025-08-27 11:33:27 +09:00
variant={page === currentPage ? "default" : "ghost"}
2025-08-21 09:41:46 +09:00
size="sm"
onClick={() => handlePageChange(page as number)}
2025-08-27 11:33:27 +09:00
className="h-8 w-8 p-0 lg:h-9 lg:w-9"
2025-08-21 09:41:46 +09:00
>
{page}
</Button>
)}
</div>
))}
{/* 다음 페이지 */}
<Button
2025-08-27 11:33:27 +09:00
variant="ghost"
2025-08-21 09:41:46 +09:00
size="sm"
onClick={() => handlePageChange(currentPage + 1)}
disabled={currentPage === totalPages}
2025-08-27 11:33:27 +09:00
className="h-8 w-8 p-0 lg:h-9 lg:w-9"
2025-08-21 09:41:46 +09:00
title="다음 페이지"
>
2025-08-27 11:33:27 +09:00
<ChevronRight className="h-3 w-3 lg:h-4 lg:w-4" />
2025-08-21 09:41:46 +09:00
</Button>
{/* 마지막 페이지로 */}
<Button
2025-08-27 11:33:27 +09:00
variant="ghost"
2025-08-21 09:41:46 +09:00
size="sm"
onClick={() => handlePageChange(totalPages)}
disabled={currentPage === totalPages}
2025-08-27 11:33:27 +09:00
className="h-8 w-8 p-0 lg:h-9 lg:w-9"
2025-08-21 09:41:46 +09:00
title="마지막 페이지"
>
2025-08-27 11:33:27 +09:00
<ChevronsRight className="h-3 w-3 lg:h-4 lg:w-4" />
2025-08-21 09:41:46 +09:00
</Button>
</div>
</div>
</div>
);
}