table and filter reusable component

This commit is contained in:
hamid zarghami
2025-04-28 14:13:11 +03:30
parent 53212445c6
commit d11837fd3f
18 changed files with 706 additions and 55 deletions
+146
View File
@@ -0,0 +1,146 @@
import React, { Fragment, ReactNode } from 'react';
import DefaultTableSkeleton from './DefaultTableSkeleton';
import Td from './Td';
export type RowDataType = Record<string, unknown> & { id: string | number };
export interface ColumnType<T extends RowDataType = RowDataType> {
title: string;
key: string;
render?: (item: T) => React.ReactNode;
width?: string | number;
align?: 'left' | 'center' | 'right';
sortable?: boolean;
className?: string;
}
export interface TableProps<T extends RowDataType = RowDataType> {
columns: ColumnType<T>[];
data: T[];
isLoading?: boolean;
onRowClick?: (id: T['id'], item: T) => void;
className?: string;
rowClassName?: string | ((item: T, index: number) => string);
noDataMessage?: React.ReactNode;
headerClassName?: string;
emptyRowsCount?: number;
showHeader?: boolean;
actions?: ReactNode;
actionsClassName?: string;
actionsPosition?: 'top' | 'header-replace' | 'above-header';
}
const Table = <T extends RowDataType>({
columns,
data,
isLoading = false,
onRowClick,
className = '',
rowClassName = '',
noDataMessage = 'هیچ داده‌ای یافت نشد',
headerClassName = 'bg-gray-50',
emptyRowsCount = 5,
showHeader = true,
actions = null,
actionsPosition = 'top',
}: TableProps<T>): React.ReactElement => {
const getRowClassName = (item: T, index: number): string => {
if (typeof rowClassName === 'function') {
return rowClassName(item, index);
}
return rowClassName;
};
const getCellClassName = (column: ColumnType<T>): string => {
const alignClass = column.align ? `text-${column.align}` : '';
return `p-3 ${alignClass} ${column.className || ''}`.trim();
};
const renderTableBody = () => {
if (isLoading) {
return (
<Fragment>
<DefaultTableSkeleton tdCount={columns.length} trCount={emptyRowsCount} />
</Fragment>
);
}
if (data.length === 0) {
return (
<tr>
<td colSpan={columns.length} className="p-8 text-center text-gray-500">
{noDataMessage}
</td>
</tr>
);
}
return data.map((item, rowIndex) => (
<tr
key={item.id}
className={`tr border-b hover:bg-gray-50 ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)}`}
onClick={() => onRowClick && onRowClick(item.id, item)}
>
{columns.map((col) => (
<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>
))}
</tr>
));
};
const renderHeader = () => {
if (!showHeader || actionsPosition === 'header-replace') return null;
return (
<thead className={`thead ${headerClassName}`}>
<tr>
{columns.map((col) => (
<th
key={col.key}
className={getCellClassName(col)}
style={{ width: col.width }}
>
<Td text={col.title} />
</th>
))}
</tr>
</thead>
);
};
const renderActions = () => {
if (!actions) return null;
return (
<div className={'action'}>
{actions}
</div>
);
};
return (
<div className={`relative overflow-x-auto rounded-3xl 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>
);
};
export default Table;