import React, { Fragment, useState, useRef } from 'react'; import DefaultTableSkeleton from './DefaultTableSkeleton'; import Td from './Td'; import { TableProps, RowDataType, ColumnType } from './types/TableTypes'; import { Checkbox } from './ui/checkbox'; import RowActionsDropdown from './RowActionsDropdown'; import Pagination from './Pagination'; import NoData from '@/assets/images/empty.svg'; const Table = ({ columns, data, isLoading = false, onRowClick, className = '', rowClassName = '', noDataMessage = 'هیچ داده‌ای یافت نشد.', headerClassName = 'bg-gray-50', emptyRowsCount = 5, showHeader = true, actions = null, actionsPosition = 'top', selectable = false, selectedActions = null, onSelectionChange, selectedRows: externalSelectedRows, clearSelection = false, rowActions, inlineActions, pagination, }: TableProps): React.ReactElement => { const [internalSelectedRows, setInternalSelectedRows] = useState([]); const [mobileSelectionMode, setMobileSelectionMode] = useState(false); const longPressTimer = useRef(null); const [longPressActiveId, setLongPressActiveId] = useState(null); // استفاده از selectedRows خارجی اگر موجود باشد، در غیر این صورت از internal استفاده کن const selectedRows = externalSelectedRows !== undefined ? externalSelectedRows : internalSelectedRows; // پاک کردن انتخاب‌ها وقتی clearSelection تغییر کند React.useEffect(() => { if (clearSelection && externalSelectedRows === undefined) { setInternalSelectedRows([]); } // خروج از حالت انتخاب موبایل هم if (clearSelection) { setMobileSelectionMode(false); } }, [clearSelection, externalSelectedRows]); // تمیز کردن timer هنگام unmount React.useEffect(() => { return () => { if (longPressTimer.current) { clearTimeout(longPressTimer.current); } }; }, []); // خروج از حالت انتخاب اگر هیچ آیتمی انتخاب نشده، ورود اگر آیتمی انتخاب شده React.useEffect(() => { if (selectedRows.length === 0 && mobileSelectionMode) { setMobileSelectionMode(false); } else if (selectedRows.length > 0 && !mobileSelectionMode) { setMobileSelectionMode(true); } }, [selectedRows.length, mobileSelectionMode]); const handleLongPressStart = (item: T) => { if (!selectable || mobileSelectionMode) return; setLongPressActiveId(item.id); longPressTimer.current = window.setTimeout(() => { setMobileSelectionMode(true); // انتخاب آیتم فعلی handleSingleRowSelect(item, true); setLongPressActiveId(null); }, 500); // 500ms برای long press }; const handleLongPressEnd = () => { if (longPressTimer.current) { clearTimeout(longPressTimer.current); longPressTimer.current = null; } setLongPressActiveId(null); }; const handleMobileRowClick = (item: T) => { if (mobileSelectionMode) { // در حالت انتخاب، آیتم را انتخاب/عدم انتخاب کن const isSelected = isRowSelected(item); handleSingleRowSelect(item, !isSelected); } else if (longPressActiveId !== item.id) { // در حالت عادی، row click عادی را اجرا کن handleRowClick(item); } }; const getRowClassName = (item: T, index: number): string => { if (typeof rowClassName === 'function') { return rowClassName(item, index); } return rowClassName; }; const getCellClassName = (column: ColumnType): string => { const alignClass = column.align ? `text-${column.align}` : ''; return `px-3 md:px-6 py-3 md:py-4 whitespace-nowrap text-xs ${alignClass} ${column.className || ''}`.trim(); }; const handleRowClick = (item: T) => { if (onRowClick) { onRowClick(item.id, item); } }; const handleSelectAll = () => { const newSelectedRows = selectedRows.length === data.length ? [] : [...data]; if (externalSelectedRows !== undefined) { // اگر selectedRows از خارج کنترل می‌شود، فقط callback را فراخوانی کن if (onSelectionChange) { onSelectionChange(newSelectedRows); } } else { // در غیر این صورت internal state را به‌روزرسانی کن setInternalSelectedRows(newSelectedRows); if (onSelectionChange) { onSelectionChange(newSelectedRows); } } }; const handleSingleRowSelect = (item: T, checked: boolean) => { const newSelectedRows = checked ? [...selectedRows, item] : selectedRows.filter((row) => row.id !== item.id); if (externalSelectedRows !== undefined) { // اگر selectedRows از خارج کنترل می‌شود، فقط callback را فراخوانی کن if (onSelectionChange) { onSelectionChange(newSelectedRows); } } else { // در غیر این صورت internal state را به‌روزرسانی کن setInternalSelectedRows(newSelectedRows); if (onSelectionChange) { onSelectionChange(newSelectedRows); } } }; const isRowSelected = (item: T): boolean => { return selectedRows.some((row) => row.id === item.id); }; // Gmail-style layout برای وقتی header نمایش داده نمی‌شود const renderGmailStyleRows = () => { const hasActions = (actionsPosition === 'top' || actionsPosition === 'above-header') && (actions || (selectable && selectedRows.length > 0 && selectedActions)); const roundedClass = hasActions ? 'rounded-b-2xl' : 'rounded-2xl'; if (isLoading) { return (
{Array.from({ length: emptyRowsCount }).map((_, index) => (
))}
); } if (data.length === 0) { return (
no data
{noDataMessage}
); } return (
{data.map((item, rowIndex) => { // Gmail-style: خوانده نشده = پس‌زمینه سفید، خوانده شده = پس‌زمینه خاکستری const isUnread = 'seen' in item && !item.seen; const bgColor = isUnread ? 'bg-white' : 'bg-gray-50'; const hoverColor = isUnread ? 'hover:bg-gray-50' : 'hover:bg-gray-100'; const fontWeight = isUnread ? 'font-semibold' : 'font-normal'; return (
handleMobileRowClick(item)} onTouchStart={() => handleLongPressStart(item)} onTouchEnd={handleLongPressEnd} onTouchCancel={handleLongPressEnd} onMouseDown={() => handleLongPressStart(item)} onMouseUp={handleLongPressEnd} onMouseLeave={handleLongPressEnd} > {selectable && mobileSelectionMode && (
e.stopPropagation()} > handleSingleRowSelect(item, !!checked)} />
)} {/* Inline Actions - بعد از checkbox */}
{/* محتوای اصلی - ستون اول */}
{columns[0]?.render ? columns[0].render(item) : (item[columns[0]?.key] as React.ReactNode)}
{/* اطلاعات اضافی - سه ستون آخر در یک خط */} {columns.length > 1 && (
{columns.slice(1).map((col) => (
{col.render ? col.render(item) : (item[col.key] as React.ReactNode)}
))}
)}
{rowActions && (
e.stopPropagation()} >
)}
); })}
); }; const renderTableBody = () => { if (isLoading) { return ( ); } if (data.length === 0) { return ( no data
{noDataMessage}
); } return data.map((item, rowIndex) => { // Gmail-style: خوانده نشده = پس‌زمینه سفید، خوانده شده = پس‌زمینه خاکستری const isUnread = 'seen' in item && !item.seen; const bgColor = isUnread ? 'bg-white' : 'bg-gray-50'; const hoverColor = isUnread ? 'hover:bg-gray-50' : 'hover:bg-gray-100'; const fontWeight = isUnread ? 'font-semibold' : 'font-normal'; return ( handleRowClick(item)} > {selectable && ( e.stopPropagation()} >
handleSingleRowSelect(item, !!checked)} /> { inlineActions && (
{inlineActions(item).map((action, actionIndex) => (
{action.icon}
))}
) }
)} {columns.map((col) => ( {col.render ? col.render(item) : item[col.key] as React.ReactNode} ))} {rowActions && ( e.stopPropagation()} > )} ); }); }; const renderHeader = () => { if (!showHeader || actionsPosition === 'header-replace') return null; return ( {selectable && ( e.stopPropagation()} > 0 && selectedRows.length === data.length} onCheckedChange={handleSelectAll} /> )} {inlineActions && ( عملیات )} {columns.map((col) => ( ))} {rowActions && ( )} ); }; const renderActions = () => { if (!actions && (!selectable || selectedRows.length === 0 || !selectedActions)) return null; return (
{actions} {selectable && selectedRows.length > 0 && selectedActions && selectedActions}
); }; // اگر header نشان داده نمی‌شود، از Gmail-style layout فقط در موبایل استفاده کن if (!showHeader) { return (
{/* نسخه موبایل - Gmail style */}
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
{actions} {selectable && selectedRows.length > 0 && selectedActions && selectedActions}
{mobileSelectionMode && ( )}
)} {renderGmailStyleRows()}
{/* نسخه دسکتاپ - Table معمولی */}
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
{actions} {selectable && selectedRows.length > 0 && selectedActions && selectedActions}
)}
0 && selectedActions)) ? 'rounded-b-3xl' : 'rounded-3xl'} bg-white`}> {renderTableBody()}
{pagination && ( )}
); } return (
{(actionsPosition === 'top' || actionsPosition === 'above-header') && renderActions()}
{renderHeader()} {actionsPosition === 'header-replace' && ( )} {renderTableBody()}
{renderActions()}
{pagination && (() => { console.log('Table component rendering pagination:', pagination); return ( ); })()}
); }; export default Table; /* مثال استفاده با pagination: import Table from '@/components/Table'; import { useState } from 'react'; const MyComponent = () => { const [currentPage, setCurrentPage] = useState(1); const totalPages = 10; const columns = [ { title: 'نام', key: 'name' }, { title: 'ایمیل', key: 'email' }, ]; const data = [ { id: 1, name: 'علی', email: 'ali@example.com' }, { id: 2, name: 'فاطمه', email: 'fateme@example.com' }, ]; const handlePageChange = (page: number) => { setCurrentPage(page); // اینجا می‌توانید API call جدید برای دریافت داده‌های صفحه جدید بزنید }; return ( ); }; مثال استفاده با inline actions: import Table from '@/components/Table'; import { Star1, Archive } from 'iconsax-react'; const MyEmailComponent = () => { const columns = [ { title: 'موضوع', key: 'subject' }, { title: 'فرستنده', key: 'from' }, ]; const data = [ { id: 1, subject: 'سلام', from: 'ali@example.com', flagged: false }, { id: 2, subject: 'خبرنامه', from: 'newsletter@example.com', flagged: true }, ]; const getInlineActions = (item) => [ { icon: , onClick: () => handleFavoriteToggle(item.id), tooltip: item.flagged ? 'حذف از علاقه‌مندی‌ها' : 'اضافه به علاقه‌مندی‌ها' }, { icon: , onClick: () => handleArchive(item.id), tooltip: 'بایگانی' } ]; return (
); }; */