import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, Search, } from 'lucide-react'; import * as React from 'react'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; export interface PaginationState { current_page: number; last_page: number; per_page: number; total: number; } interface CardRenderContext { item: TData; index: number; isExpanded: boolean; onToggleExpand: () => void; } interface CardTableProps { data: TData[]; getItemKey: (item: TData) => number | string; renderCard: (ctx: CardRenderContext) => React.ReactNode; renderSubContent: (item: TData) => React.ReactNode; expandedKeys: Set | 'all'; onToggleExpand: (key: number | string) => void; searchPlaceholder?: string; searchValue?: string; onSearchChange?: (value: string) => void; toolbar?: React.ReactNode; pagination?: PaginationState; onPageChange?: (page: number) => void; onPerPageChange?: (perPage: number) => void; emptyText?: string; rowClassName?: (item: TData) => string; } function useDebounce(callback: (value: string) => void, delay: number) { const timeoutRef = React.useRef | null>(null); return React.useCallback( (value: string) => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } timeoutRef.current = setTimeout(() => { callback(value); }, delay); }, [callback, delay], ); } export function CardTable({ data, getItemKey, renderCard, renderSubContent, expandedKeys, onToggleExpand, searchPlaceholder = 'Ketikkan sesuatu...', searchValue, onSearchChange, toolbar, pagination, onPageChange, onPerPageChange, emptyText = 'Tidak ada data.', rowClassName, }: CardTableProps) { const [localSearch, setLocalSearch] = React.useState(searchValue ?? ''); React.useEffect(() => { setLocalSearch(searchValue ?? ''); }, [searchValue]); const isServerMode = !!pagination && !!onPageChange; const handleSearchDebounced = useDebounce( (value: string) => onSearchChange?.(value), 300, ); function handleSearchChange(value: string) { setLocalSearch(value); if (isServerMode) { handleSearchDebounced(value); } } function isItemExpanded(key: number | string): boolean { if (expandedKeys === 'all') { return true; } return expandedKeys.has(key); } const totalPages = pagination?.last_page ?? 1; const currentPage = pagination?.current_page ?? 1; return ( <> {(onSearchChange || toolbar || isServerMode) && (
{onSearchChange && (
handleSearchChange(e.target.value) } className="pl-9" />
)} {toolbar}
{isServerMode && onPerPageChange && ( )}
)}
{data.length === 0 ? ( {emptyText} ) : ( data.map((item, index) => { const key = getItemKey(item); const isExpanded = isItemExpanded(key); return (
{renderCard({ item, index, isExpanded, onToggleExpand: () => onToggleExpand(key), })} {isExpanded && (
{renderSubContent(item)}
)}
); }) )}
{isServerMode && (
Halaman {currentPage} dari {totalPages}
)} ); }