reponsive and selective actions and row actioin + pagination

This commit is contained in:
hamid zarghami
2025-06-17 20:52:40 +03:30
parent 71fd53c810
commit 4c869c42c6
24 changed files with 869 additions and 287 deletions
+119 -50
View File
@@ -3,6 +3,8 @@ 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';
const Table = <T extends RowDataType>({
columns,
@@ -18,6 +20,10 @@ const Table = <T extends RowDataType>({
actions = null,
actionsPosition = 'top',
selectable = false,
selectedActions = null,
onSelectionChange,
rowActions,
pagination,
}: TableProps<T>): React.ReactElement => {
const [selectedRows, setSelectedRows] = useState<T[]>([]);
@@ -31,36 +37,42 @@ const Table = <T extends RowDataType>({
const getCellClassName = (column: ColumnType<T>): string => {
const alignClass = column.align ? `text-${column.align}` : '';
return `p-3 ${alignClass} ${column.className || ''}`.trim();
return `px-3 md:px-6 py-3 md:py-4 whitespace-nowrap text-xs ${alignClass} ${column.className || ''}`.trim();
};
const handleRowClick = (item: T) => {
if (selectable) {
if (selectedRows.includes(item)) {
setSelectedRows(selectedRows.filter((row) => row !== item));
} else {
setSelectedRows([...selectedRows, item]);
}
}
if (onRowClick) {
onRowClick(item.id, item);
}
};
const handleSelectAll = () => {
if (selectedRows.length === data.length) {
setSelectedRows([]);
} else {
setSelectedRows([...data]);
const newSelectedRows = selectedRows.length === data.length ? [] : [...data];
setSelectedRows(newSelectedRows);
if (onSelectionChange) {
onSelectionChange(newSelectedRows);
}
};
const handleSingleRowSelect = (item: T, checked: boolean) => {
const newSelectedRows = checked
? [...selectedRows, item]
: selectedRows.filter((row) => row.id !== item.id);
setSelectedRows(newSelectedRows);
if (onSelectionChange) {
onSelectionChange(newSelectedRows);
}
};
const isRowSelected = (item: T): boolean => {
return selectedRows.some((row) => row.id === item.id);
};
const renderTableBody = () => {
if (isLoading) {
return (
<Fragment>
<DefaultTableSkeleton tdCount={columns.length} trCount={emptyRowsCount} />
<DefaultTableSkeleton tdCount={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} trCount={emptyRowsCount} />
</Fragment>
);
}
@@ -68,7 +80,7 @@ const Table = <T extends RowDataType>({
if (data.length === 0) {
return (
<tr>
<td colSpan={columns.length} className="p-8 text-center text-gray-500">
<td colSpan={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} className="px-3 md:px-6 py-6 md:py-8 text-center text-gray-500">
{noDataMessage}
</td>
</tr>
@@ -77,28 +89,31 @@ const Table = <T extends RowDataType>({
return data.map((item, rowIndex) => (
<tr
key={item.id}
className={`tr border-b hover:bg-gray-50 ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)}`}
className={`w-full h-[64px] md:h-[74px] bg-white border-t border-[#EAEDF5] hover:bg-[#F4F5F9] ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)}`}
onClick={() => handleRowClick(item)}
>
{selectable && (
<td className="p-3 w-10 relative z-[5]">
<td className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]">
<Checkbox
checked={selectedRows.includes(item)}
onCheckedChange={(checked) => {
if (checked) {
setSelectedRows([...selectedRows, item]);
} else {
setSelectedRows(selectedRows.filter((row) => row !== item));
}
}}
checked={isRowSelected(item)}
onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)}
/>
</td>
)}
{columns.map((col) => (
<td key={`${item.id}-${col.key}`} className={getCellClassName(col)} style={{ width: col.width }}>
<td
key={`${item.id}-${col.key}`}
className={getCellClassName(col)}
style={{ width: col.width }}
>
{col.render ? col.render(item) : item[col.key] as React.ReactNode}
</td>
))}
{rowActions && (
<td className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]">
<RowActionsDropdown actions={rowActions(item)} />
</td>
)}
</tr>
));
};
@@ -107,12 +122,10 @@ const Table = <T extends RowDataType>({
if (!showHeader || actionsPosition === 'header-replace') return null;
return (
<thead className={`thead ${headerClassName}`}>
<thead className={`h-[60px] md:h-[69px] bg-white text-sm text-[#8C90A3] ${headerClassName}`}>
<tr>
{selectable && (
<th
className="p-3 w-10 relative z-[5]"
>
<th className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]">
<Checkbox
checked={data.length > 0 && selectedRows.length === data.length}
onCheckedChange={handleSelectAll}
@@ -128,41 +141,97 @@ const Table = <T extends RowDataType>({
<Td text={col.title} />
</th>
))}
{rowActions && (
<th className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10"></th>
)}
</tr>
</thead>
);
};
const renderActions = () => {
if (!actions) return null;
if (!actions && (!selectable || selectedRows.length === 0 || !selectedActions)) return null;
return (
<div className={'action'}>
{actions}
<div className="h-[64px] rounded-t-2xl md:rounded-t-3xl md:h-[74px] bg-white flex items-center px-3 md:px-6">
<div className='flex items-center gap-2 md:gap-4'>
{actions}
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
</div>
</div>
);
};
return (
<div className={`relative overflow-x-auto rounded-3xl mt-9 w-full ${className}`}>
<div className={`relative mt-6 md:mt-9 w-full ${className}`}>
{(actionsPosition === 'top' || actionsPosition === 'above-header') && renderActions()}
<table className="w-full text-sm border-collapse">
{renderHeader()}
{actionsPosition === 'header-replace' && (
<thead>
<tr>
<th colSpan={columns.length} className="p-0">
{renderActions()}
</th>
</tr>
</thead>
)}
<tbody>
{renderTableBody()}
</tbody>
</table>
<div className="overflow-x-auto overflow-hidden rounded-b-2xl md:rounded-b-3xl bg-white shadow-sm">
<table className="w-full text-sm border-collapse min-w-[600px]">
{renderHeader()}
{actionsPosition === 'header-replace' && (
<thead>
<tr>
<th colSpan={columns.length} className="p-0">
{renderActions()}
</th>
</tr>
</thead>
)}
<tbody>
{renderTableBody()}
</tbody>
</table>
</div>
{pagination && (
<Pagination
currentPage={pagination.currentPage}
totalPages={pagination.totalPages}
onPageChange={pagination.onPageChange}
/>
)}
</div>
);
};
export default Table;
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 (
<Table
columns={columns}
data={data}
pagination={{
currentPage,
totalPages,
onPageChange: handlePageChange
}}
/>
);
};
*/