This commit is contained in:
@@ -48,7 +48,8 @@ export function DataTableCard<T>({
|
||||
pageSize = 8,
|
||||
minWidth = 980,
|
||||
loading = false,
|
||||
emptyMessage = '데이터가 없습니다.'
|
||||
emptyMessage = '데이터가 없습니다.',
|
||||
serverPagination
|
||||
}: {
|
||||
title: string;
|
||||
description?: string;
|
||||
@@ -62,12 +63,23 @@ export function DataTableCard<T>({
|
||||
minWidth?: number;
|
||||
loading?: boolean;
|
||||
emptyMessage?: string;
|
||||
serverPagination?: {
|
||||
page: number;
|
||||
total: number;
|
||||
onPageChange: (page: number) => void;
|
||||
onSearchChange?: (keyword: string) => void;
|
||||
filterValues?: Record<string, string>;
|
||||
onFilterChange?: (key: string, value: string) => void;
|
||||
};
|
||||
}) {
|
||||
const [search, setSearch] = useState('');
|
||||
const [filterValues, setFilterValues] = useState<Record<string, string>>({});
|
||||
const [page, setPage] = useState(1);
|
||||
const serverMode = Boolean(serverPagination);
|
||||
const activeFilterValues = serverPagination?.filterValues ?? filterValues;
|
||||
|
||||
const filteredRows = useMemo(() => {
|
||||
if (serverMode) return rows;
|
||||
const normalizedSearch = search.trim().toLowerCase();
|
||||
|
||||
return rows.filter((row) => {
|
||||
@@ -75,24 +87,37 @@ export function DataTableCard<T>({
|
||||
? getSearchText(row).toLowerCase().includes(normalizedSearch)
|
||||
: true;
|
||||
const matchesFilters = filters.every((filter) => {
|
||||
const selected = filterValues[filter.key];
|
||||
const selected = activeFilterValues[filter.key];
|
||||
return !selected || filter.getValue(row) === selected;
|
||||
});
|
||||
|
||||
return matchesSearch && matchesFilters;
|
||||
});
|
||||
}, [filterValues, filters, getSearchText, rows, search]);
|
||||
}, [activeFilterValues, filters, getSearchText, rows, search, serverMode]);
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(filteredRows.length / pageSize));
|
||||
const currentPage = Math.min(page, totalPages);
|
||||
const pageRows = filteredRows.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
||||
const totalCount = serverPagination?.total ?? filteredRows.length;
|
||||
const totalPages = Math.max(1, Math.ceil(totalCount / pageSize));
|
||||
const currentPage = Math.min(serverPagination?.page ?? page, totalPages);
|
||||
const pageRows = serverMode ? rows : filteredRows.slice((currentPage - 1) * pageSize, currentPage * pageSize);
|
||||
const flexibleColumnIndex = columns[0]?.key === 'no' && columns.length > 1 ? 1 : -1;
|
||||
|
||||
const updateFilter = (key: string, value: string) => {
|
||||
if (serverPagination?.onFilterChange) {
|
||||
serverPagination.onFilterChange(key, value);
|
||||
return;
|
||||
}
|
||||
setFilterValues((prev) => ({ ...prev, [key]: value }));
|
||||
setPage(1);
|
||||
};
|
||||
|
||||
const movePage = (nextPage: number) => {
|
||||
if (serverPagination) {
|
||||
serverPagination.onPageChange(nextPage);
|
||||
return;
|
||||
}
|
||||
setPage(nextPage);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="min-w-0 overflow-hidden">
|
||||
<div className="flex flex-col gap-3 border-b border-border px-5 py-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
@@ -108,7 +133,9 @@ export function DataTableCard<T>({
|
||||
placeholder={searchPlaceholder}
|
||||
value={search}
|
||||
onChange={(event) => {
|
||||
setSearch(event.target.value);
|
||||
const value = event.target.value;
|
||||
setSearch(value);
|
||||
serverPagination?.onSearchChange?.(value);
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
@@ -120,7 +147,7 @@ export function DataTableCard<T>({
|
||||
{filter.label}
|
||||
<select
|
||||
className="h-9 rounded-md border border-input bg-background px-3 text-sm font-medium text-foreground outline-none focus:ring-2 focus:ring-ring"
|
||||
value={filterValues[filter.key] ?? ''}
|
||||
value={activeFilterValues[filter.key] ?? ''}
|
||||
onChange={(event) => updateFilter(filter.key, event.target.value)}
|
||||
>
|
||||
<option value="">전체</option>
|
||||
@@ -210,14 +237,14 @@ export function DataTableCard<T>({
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-4 border-t border-border px-5 py-3 text-sm">
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{loading ? '불러오는 중' : `총 ${filteredRows.length}건 · ${currentPage} / ${totalPages}`}
|
||||
{loading ? '불러오는 중' : `총 ${totalCount}건 · ${currentPage} / ${totalPages}`}
|
||||
</span>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-8 px-3 text-[13px]"
|
||||
disabled={currentPage <= 1}
|
||||
onClick={() => setPage((prev) => Math.max(1, prev - 1))}
|
||||
onClick={() => movePage(Math.max(1, currentPage - 1))}
|
||||
>
|
||||
<ChevronLeft className="size-4" />
|
||||
이전
|
||||
@@ -226,7 +253,7 @@ export function DataTableCard<T>({
|
||||
variant="outline"
|
||||
className="h-8 px-3 text-[13px]"
|
||||
disabled={currentPage >= totalPages}
|
||||
onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
|
||||
onClick={() => movePage(Math.min(totalPages, currentPage + 1))}
|
||||
>
|
||||
다음
|
||||
<ChevronRight className="size-4" />
|
||||
|
||||
Reference in New Issue
Block a user