email actions + sent list + action list + archive list + trash list
This commit is contained in:
+129
-83
@@ -22,11 +22,23 @@ const Table = <T extends RowDataType>({
|
||||
selectable = false,
|
||||
selectedActions = null,
|
||||
onSelectionChange,
|
||||
selectedRows: externalSelectedRows,
|
||||
clearSelection = false,
|
||||
rowActions,
|
||||
pagination,
|
||||
}: TableProps<T>): React.ReactElement => {
|
||||
|
||||
const [selectedRows, setSelectedRows] = useState<T[]>([]);
|
||||
const [internalSelectedRows, setInternalSelectedRows] = useState<T[]>([]);
|
||||
|
||||
// استفاده از selectedRows خارجی اگر موجود باشد، در غیر این صورت از internal استفاده کن
|
||||
const selectedRows = externalSelectedRows !== undefined ? externalSelectedRows : internalSelectedRows;
|
||||
|
||||
// پاک کردن انتخابها وقتی clearSelection تغییر کند
|
||||
React.useEffect(() => {
|
||||
if (clearSelection && externalSelectedRows === undefined) {
|
||||
setInternalSelectedRows([]);
|
||||
}
|
||||
}, [clearSelection, externalSelectedRows]);
|
||||
|
||||
const getRowClassName = (item: T, index: number): string => {
|
||||
if (typeof rowClassName === 'function') {
|
||||
@@ -48,9 +60,18 @@ const Table = <T extends RowDataType>({
|
||||
|
||||
const handleSelectAll = () => {
|
||||
const newSelectedRows = selectedRows.length === data.length ? [] : [...data];
|
||||
setSelectedRows(newSelectedRows);
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
|
||||
if (externalSelectedRows !== undefined) {
|
||||
// اگر selectedRows از خارج کنترل میشود، فقط callback را فراخوانی کن
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
}
|
||||
} else {
|
||||
// در غیر این صورت internal state را بهروزرسانی کن
|
||||
setInternalSelectedRows(newSelectedRows);
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -58,9 +79,18 @@ const Table = <T extends RowDataType>({
|
||||
const newSelectedRows = checked
|
||||
? [...selectedRows, item]
|
||||
: selectedRows.filter((row) => row.id !== item.id);
|
||||
setSelectedRows(newSelectedRows);
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
|
||||
if (externalSelectedRows !== undefined) {
|
||||
// اگر selectedRows از خارج کنترل میشود، فقط callback را فراخوانی کن
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
}
|
||||
} else {
|
||||
// در غیر این صورت internal state را بهروزرسانی کن
|
||||
setInternalSelectedRows(newSelectedRows);
|
||||
if (onSelectionChange) {
|
||||
onSelectionChange(newSelectedRows);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,52 +123,60 @@ const Table = <T extends RowDataType>({
|
||||
|
||||
return (
|
||||
<div className={`bg-white ${roundedClass} overflow-hidden`}>
|
||||
{data.map((item, rowIndex) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`w-full min-h-[48px] hover:bg-[#F4F5F9] ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)} px-4 py-3 flex items-center gap-3 ${rowIndex !== 0 ? 'border-t border-[#EAEDF5]' : ''}`}
|
||||
onClick={() => handleRowClick(item)}
|
||||
>
|
||||
{selectable && (
|
||||
<div
|
||||
className="flex-shrink-0 relative z-[5]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isRowSelected(item)}
|
||||
onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{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';
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* محتوای اصلی - ستون اول */}
|
||||
<div className="flex items-center mb-0.5 text-sm font-medium">
|
||||
{columns[0]?.render ? columns[0].render(item) : (item[columns[0]?.key] as React.ReactNode)}
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className={`w-full min-h-[48px] ${bgColor} ${hoverColor} ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)} px-4 py-3 flex items-center gap-3 ${rowIndex !== 0 ? 'border-t border-[#EAEDF5]' : ''}`}
|
||||
onClick={() => handleRowClick(item)}
|
||||
>
|
||||
{selectable && (
|
||||
<div
|
||||
className="flex-shrink-0 relative z-[5]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isRowSelected(item)}
|
||||
onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
{/* محتوای اصلی - ستون اول */}
|
||||
<div className={`flex items-center mb-0.5 text-sm ${fontWeight}`}>
|
||||
{columns[0]?.render ? columns[0].render(item) : (item[columns[0]?.key] as React.ReactNode)}
|
||||
</div>
|
||||
|
||||
{/* اطلاعات اضافی - سه ستون آخر در یک خط */}
|
||||
{columns.length > 1 && (
|
||||
<div className={`flex items-center gap-3 text-xs text-gray-500 ${fontWeight}`}>
|
||||
{columns.slice(1).map((col) => (
|
||||
<div key={col.key} className="truncate">
|
||||
{col.render ? col.render(item) : (item[col.key] as React.ReactNode)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* اطلاعات اضافی - سه ستون آخر در یک خط */}
|
||||
{columns.length > 1 && (
|
||||
<div className="flex items-center gap-3 text-xs text-gray-500">
|
||||
{columns.slice(1).map((col) => (
|
||||
<div key={col.key} className="truncate">
|
||||
{col.render ? col.render(item) : (item[col.key] as React.ReactNode)}
|
||||
</div>
|
||||
))}
|
||||
{rowActions && (
|
||||
<div
|
||||
className="flex-shrink-0 relative z-[5]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<RowActionsDropdown actions={rowActions(item)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{rowActions && (
|
||||
<div
|
||||
className="flex-shrink-0 relative z-[5]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<RowActionsDropdown actions={rowActions(item)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -161,42 +199,50 @@ const Table = <T extends RowDataType>({
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
return data.map((item, rowIndex) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
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="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Checkbox
|
||||
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 }}
|
||||
>
|
||||
{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]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<RowActionsDropdown actions={rowActions(item)} />
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
));
|
||||
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 (
|
||||
<tr
|
||||
key={item.id}
|
||||
className={`w-full h-[64px] md:h-[74px] ${bgColor} border-t border-[#EAEDF5] ${hoverColor} ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)}`}
|
||||
onClick={() => handleRowClick(item)}
|
||||
>
|
||||
{selectable && (
|
||||
<td
|
||||
className="px-3 md:px-6 py-3 md:py-4 w-8 md:w-10 relative z-[5]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isRowSelected(item)}
|
||||
onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)}
|
||||
/>
|
||||
</td>
|
||||
)}
|
||||
{columns.map((col) => (
|
||||
<td
|
||||
key={`${item.id}-${col.key}`}
|
||||
className={`${getCellClassName(col)} ${fontWeight}`}
|
||||
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]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<RowActionsDropdown actions={rowActions(item)} />
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const renderHeader = () => {
|
||||
|
||||
@@ -11,7 +11,7 @@ import Button from '../Button'
|
||||
import Select from '../Select'
|
||||
import UploadButton from '../UploadButton'
|
||||
import { toast } from '../Toast'
|
||||
import { ErrorType } from '@/helpers/types'
|
||||
|
||||
|
||||
|
||||
const NewMessage: FC = () => {
|
||||
@@ -188,9 +188,6 @@ const NewMessage: FC = () => {
|
||||
handleClose()
|
||||
resetForm()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error.message[0], 'error')
|
||||
},
|
||||
onSettled: () => {
|
||||
// Reset loading states
|
||||
setIsSending(false)
|
||||
|
||||
@@ -36,6 +36,8 @@ export interface TableProps<T extends RowDataType = RowDataType> {
|
||||
selectable?: boolean;
|
||||
selectedActions?: ReactNode;
|
||||
onSelectionChange?: (selectedRows: T[]) => void;
|
||||
selectedRows?: T[];
|
||||
clearSelection?: boolean;
|
||||
rowActions?: (item: T) => RowActionItem[];
|
||||
pagination?: PaginationConfig;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user