import { Button } from '@/components/ui/button'; import { Text } from '@/components/ui/text'; import { View } from '@/components/ui/view'; import { useColor } from '@/hooks/useColor'; import { BORDER_RADIUS, FONT_SIZE, HEIGHT } from '@/theme/globals'; import { ChevronDown, ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight, ChevronUp, Search, } from 'lucide-react-native'; import React, { useMemo, useState } from 'react'; import { ScrollView, TextInput, TextStyle, TouchableOpacity, ViewStyle, } from 'react-native'; // Types export interface TableColumn { id: string; header: string; accessorKey: string; sortable?: boolean; filterable?: boolean; width?: number | string; minWidth?: number; cell?: (value: any, row: T) => React.ReactNode; headerCell?: () => React.ReactNode; align?: 'left' | 'center' | 'right'; } export interface TableProps { data: T[]; columns: TableColumn[]; pagination?: boolean; pageSize?: number; searchable?: boolean; searchPlaceholder?: string; loading?: boolean; emptyMessage?: string; style?: ViewStyle; headerStyle?: ViewStyle; rowStyle?: ViewStyle; cellStyle?: ViewStyle; onRowPress?: (row: T, index: number) => void; sortable?: boolean; filterable?: boolean; } type SortDirection = 'asc' | 'desc' | null; interface SortState { column: string | null; direction: SortDirection; } export function Table({ data, columns, pagination = true, pageSize = 10, searchable = true, searchPlaceholder = 'Search...', loading = false, emptyMessage = 'No data available', style, headerStyle, rowStyle, cellStyle, onRowPress, sortable = true, filterable = true, }: TableProps) { // Theme colors const borderColor = useColor('border'); const textColor = useColor('text'); const mutedColor = useColor('textMuted'); const cardColor = useColor('card'); const primaryColor = useColor('primary'); // State const [currentPage, setCurrentPage] = useState(1); const [searchQuery, setSearchQuery] = useState(''); const [sortState, setSortState] = useState({ column: null, direction: null, }); // Filter and sort data const filteredAndSortedData = useMemo(() => { let processedData = [...data]; // Apply search filter if (searchQuery && filterable) { processedData = processedData.filter((row) => columns.some((column) => { if (!column.filterable) return false; const value = (row as any)[column.accessorKey]; return String(value || '') .toLowerCase() .includes(searchQuery.toLowerCase()); }) ); } // Apply sorting if (sortState.column && sortState.direction && sortable) { processedData.sort((a, b) => { const aValue = (a as any)[sortState.column!]; const bValue = (b as any)[sortState.column!]; if (aValue === null || aValue === undefined) return 1; if (bValue === null || bValue === undefined) return -1; if (typeof aValue === 'string' && typeof bValue === 'string') { const comparison = aValue.localeCompare(bValue); return sortState.direction === 'asc' ? comparison : -comparison; } if (aValue < bValue) return sortState.direction === 'asc' ? -1 : 1; if (aValue > bValue) return sortState.direction === 'asc' ? 1 : -1; return 0; }); } return processedData; }, [data, searchQuery, sortState, columns, filterable, sortable]); // Pagination const totalPages = pagination ? Math.ceil(filteredAndSortedData.length / pageSize) : 1; const startIndex = pagination ? (currentPage - 1) * pageSize : 0; const endIndex = pagination ? startIndex + pageSize : filteredAndSortedData.length; const paginatedData = filteredAndSortedData.slice(startIndex, endIndex); // Handlers const handleSort = (columnId: string) => { if (!sortable) return; const column = columns.find((col) => col.id === columnId); if (!column?.sortable) return; setSortState((prev) => { if (prev.column === columnId) { // Cycle through: asc -> desc -> null const newDirection: SortDirection = prev.direction === 'asc' ? 'desc' : prev.direction === 'desc' ? null : 'asc'; return { column: newDirection ? columnId : null, direction: newDirection, }; } else { return { column: columnId, direction: 'asc' }; } }); }; const handlePageChange = (page: number) => { setCurrentPage(Math.max(1, Math.min(page, totalPages))); }; const renderSortIcon = (columnId: string) => { if (!sortable) return null; const column = columns.find((col) => col.id === columnId); if (!column?.sortable) return null; if (sortState.column !== columnId) { return ( ); } return sortState.direction === 'asc' ? ( ) : ( ); }; const renderCell = (column: TableColumn, row: T, rowIndex: number) => { const value = (row as any)[column.accessorKey]; const cellContent = column.cell ? column.cell(value, row) : String(value || ''); const alignStyle: TextStyle = { textAlign: column.align || 'left', }; return ( {typeof cellContent === 'string' ? ( {cellContent} ) : ( cellContent )} ); }; const renderHeader = () => ( {columns.map((column) => ( handleSort(column.id)} disabled={!column.sortable || !sortable} > {column.headerCell ? ( column.headerCell() ) : ( <> {column.header} {renderSortIcon(column.id)} )} ))} ); const renderRow = (row: T, index: number) => ( onRowPress?.(row, index)} disabled={!onRowPress} activeOpacity={onRowPress ? 0.7 : 1} > {columns.map((column) => renderCell(column, row, index))} ); const renderPagination = () => { if (!pagination || totalPages <= 1) return null; return ( Page {currentPage} of {totalPages} ({filteredAndSortedData.length}{' '} total) ); }; const renderSearchBar = () => { if (!searchable || !filterable) return null; return ( ); }; const renderEmptyState = () => ( {emptyMessage} ); const renderLoadingState = () => ( Loading... ); return ( {renderSearchBar()} {renderHeader()} {loading ? ( renderLoadingState() ) : paginatedData.length === 0 ? ( renderEmptyState() ) : ( {paginatedData.map((row, index) => renderRow(row, index))} )} {renderPagination()} ); }