email actions + sent list + action list + archive list + trash list

This commit is contained in:
hamid zarghami
2025-07-12 16:01:17 +03:30
parent fac9159ec0
commit 663dd1a9db
45 changed files with 1947 additions and 326 deletions
+129 -83
View File
@@ -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 = () => {
+1 -4
View File
@@ -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)
+2
View File
@@ -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;
}