import { useMemo, useState, type ReactNode } from 'react'; import { ChevronLeft, ChevronRight, Search } from 'lucide-react'; import { Button, Card } from './ui'; export type DataTableColumn = { key: string; header: string; className?: string; width?: number; cell: (row: T) => ReactNode; }; export type DataTableFilter = { key: string; label: string; options: Array<{ label: string; value: string }>; getValue: (row: T) => string; }; const spacingWidthPattern = /\bw-(?:\[(\d+)px\]|(\d+))\b/; const widthClassPattern = /\bw-(?:\[[^\]]+\]|\d+)\b/g; function getColumnWidth(column: DataTableColumn) { if (column.key === 'no') return 75; if (column.width) return column.width; const widthMatch = column.className?.match(spacingWidthPattern); if (widthMatch?.[1]) return Number(widthMatch[1]); if (widthMatch?.[2]) return Number(widthMatch[2]) * 4; if (column.key === 'actions') return 80; return 160; } function stripWidthClass(className?: string) { return className?.replace(widthClassPattern, '').replace(/\s+/g, ' ').trim(); } export function DataTableCard({ title, description, rows, columns, filters = [], searchPlaceholder = '검색', getSearchText, getRowKey, pageSize = 8, minWidth = 980, loading = false, emptyMessage = '데이터가 없습니다.' }: { title: string; description?: string; rows: T[]; columns: Array>; filters?: Array>; searchPlaceholder?: string; getSearchText: (row: T) => string; getRowKey: (row: T) => string; pageSize?: number; minWidth?: number; loading?: boolean; emptyMessage?: string; }) { const [search, setSearch] = useState(''); const [filterValues, setFilterValues] = useState>({}); const [page, setPage] = useState(1); const filteredRows = useMemo(() => { const normalizedSearch = search.trim().toLowerCase(); return rows.filter((row) => { const matchesSearch = normalizedSearch ? getSearchText(row).toLowerCase().includes(normalizedSearch) : true; const matchesFilters = filters.every((filter) => { const selected = filterValues[filter.key]; return !selected || filter.getValue(row) === selected; }); return matchesSearch && matchesFilters; }); }, [filterValues, filters, getSearchText, rows, search]); 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 flexibleColumnIndex = columns[0]?.key === 'no' && columns.length > 1 ? 1 : -1; const updateFilter = (key: string, value: string) => { setFilterValues((prev) => ({ ...prev, [key]: value })); setPage(1); }; return (

{title}

{description ?

{description}

: null}
{ setSearch(event.target.value); setPage(1); }} />
{filters.length ? (
{filters.map((filter) => ( ))}
) : null}
{columns.map((column, columnIndex) => ( ))} {columns.map((column, columnIndex) => ( ))} {!loading && pageRows.map((row) => ( {columns.map((column, columnIndex) => ( ))} ))} {!loading && pageRows.length === 0 ? ( ) : null}
{column.header}
{column.key === 'no' ? ( {String((row as { no?: unknown }).no ?? '')} ) : ( column.cell(row) )}
{emptyMessage}
{loading ? '불러오는 중' : `총 ${filteredRows.length}건 · ${currentPage} / ${totalPages}`}
); }