159 lines
5.0 KiB
TypeScript
159 lines
5.0 KiB
TypeScript
import type { HTMLAttributes } from "react";
|
|
import {
|
|
flexRender,
|
|
type RowData,
|
|
type Table as TableInstance,
|
|
} from "@tanstack/react-table";
|
|
import {
|
|
ChevronLeftIcon,
|
|
ChevronRightIcon,
|
|
DoubleArrowLeftIcon,
|
|
DoubleArrowRightIcon,
|
|
} from "@radix-ui/react-icons";
|
|
import { Button, Checkbox, Label, cn } from "@common/components";
|
|
|
|
declare module "@tanstack/react-table" {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
interface ColumnMeta<TData extends RowData, TValue> {
|
|
align?: "left" | "center" | "right";
|
|
rowSpan?: number;
|
|
}
|
|
}
|
|
|
|
type GridViewDataTableProps<T> = {
|
|
table: TableInstance<T>;
|
|
variant?: "default" | "secondary" | "line";
|
|
className?: HTMLAttributes<HTMLTableElement>["className"];
|
|
viewPagination?: boolean;
|
|
onRowClick?: (row: T) => void;
|
|
};
|
|
|
|
function GridViewDataTable<T>({
|
|
table,
|
|
className,
|
|
viewPagination,
|
|
onRowClick,
|
|
}: GridViewDataTableProps<T>) {
|
|
return (
|
|
<div className="bg-secondary-background pb-10">
|
|
<div className="flex items-center gap-2 rounded-sm rounded-bl-none rounded-br-none border border-border bg-background p-4">
|
|
<Checkbox
|
|
id="all-select"
|
|
checked={table.getIsAllPageRowsSelected()}
|
|
onClick={(e) => e.stopPropagation()}
|
|
onCheckedChange={(checked) =>
|
|
table.toggleAllPageRowsSelected(!!checked)
|
|
}
|
|
/>
|
|
<Label htmlFor="all-select" className="text-2xl">
|
|
전체선택
|
|
</Label>
|
|
</div>
|
|
<div className={cn("w-full p-4", className)}>
|
|
<div className="grid grid-cols-5 gap-6">
|
|
{table.getRowModel().rows.map((row) => (
|
|
<div
|
|
key={row.id}
|
|
onClick={() => onRowClick?.(row.original)}
|
|
className={cn(
|
|
"overflow-hidden rounded-md border border-border bg-background p-4 shadow-md",
|
|
onRowClick && "cursor-pointer hover:bg-accent",
|
|
)}
|
|
>
|
|
{row.getVisibleCells().map((cell) => {
|
|
const { meta } = cell.column.columnDef;
|
|
return (
|
|
<div key={cell.id} {...meta}>
|
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
{viewPagination && <Pagination table={table} />}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function Pagination<T>({ table }: { table: TableInstance<T> }) {
|
|
const pageCount = table.getPageCount();
|
|
const currentPage = table.getState().pagination.pageIndex;
|
|
const maxVisiblePages = 10; // 한 번에 보여줄 최대 페이지 수
|
|
|
|
let startPage = Math.max(0, currentPage - maxVisiblePages / 2); // 시작 페이지
|
|
const endPage = Math.min(pageCount, startPage + maxVisiblePages); // 끝 페이지
|
|
|
|
if (endPage - startPage < maxVisiblePages) {
|
|
startPage = Math.max(0, endPage - maxVisiblePages);
|
|
}
|
|
|
|
const pageNumbers = Array(endPage - startPage)
|
|
.fill(0)
|
|
.map((_, i) => startPage + i);
|
|
|
|
return (
|
|
<div className="mt-[24px] flex items-center justify-center space-x-2">
|
|
<Button
|
|
variant="ghost"
|
|
color="white"
|
|
className="hidden size-10 p-0 lg:flex"
|
|
onClick={() => table.setPageIndex(0)}
|
|
disabled={!table.getCanPreviousPage()}
|
|
>
|
|
<span className="sr-only">첫 페이지로</span>
|
|
<DoubleArrowLeftIcon className="size-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
color="white"
|
|
className="size-10 p-0"
|
|
onClick={() => table.previousPage()}
|
|
disabled={!table.getCanPreviousPage()}
|
|
>
|
|
<span className="sr-only">이전 페이지로</span>
|
|
<ChevronLeftIcon className="size-4" />
|
|
</Button>
|
|
<div className="flex items-center justify-center text-sm font-medium">
|
|
{pageNumbers.map((pageNumber) => (
|
|
<Button
|
|
key={pageNumber}
|
|
variant="ghost"
|
|
className={cn(
|
|
"inline-block size-10 !bg-transparent p-0 text-foreground hover:!bg-accent",
|
|
currentPage === pageNumber &&
|
|
"!bg-primary !font-bold !text-primary-foreground hover:!bg-primary",
|
|
)}
|
|
onClick={() => table.setPageIndex(pageNumber)}
|
|
>
|
|
{pageNumber + 1}
|
|
</Button>
|
|
))}
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
color="white"
|
|
className="size-10 p-0"
|
|
onClick={() => table.nextPage()}
|
|
disabled={!table.getCanNextPage()}
|
|
>
|
|
<span className="sr-only">다음 페이지로</span>
|
|
<ChevronRightIcon className="size-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
color="white"
|
|
className="hidden size-10 p-0 lg:flex"
|
|
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
|
disabled={!table.getCanNextPage()}
|
|
>
|
|
<span className="sr-only">마지막 페이지로</span>
|
|
<DoubleArrowRightIcon className="size-4" />
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export { GridViewDataTable };
|