Files
dmail-front/src/pages/received/List.tsx
T
hamid zarghami 2ccc096277 fix cc and bcc
2025-10-29 11:48:31 +03:30

383 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Filters, { FilterValues } from '../../components/Filters';
import React, { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Table from '../../components/Table';
import { ColumnType } from '../../components/types/TableTypes';
import { InfoCircle, Refresh2, Trash, ArchiveTick, Star1 } from 'iconsax-react';
import RowActionsDropdown, { RowActionItem } from '../../components/RowActionsDropdown';
import { useNavigate } from 'react-router-dom';
import { useGetInbox, useMarkAllRead, useSummarizeEmail } from './hooks/useEmailData';
import { InboxMessage } from './types/Types';
import MarkAsRead from '@/assets/images/mark_as_read.svg'
import { useEmailActions } from '@/hooks/useEmailActions';
import Favorite from './components/Favorite';
import Summerize from './components/Summerize';
import Intelligense from '@/assets/images/intelligense.svg'
import SummarizeModal from './components/SummarizeModal';
import ModalConfrim from '@/components/ModalConfrim';
import { ErrorType } from '@/helpers/types';
import { toast } from '@/components/Toast';
import { Checkbox } from '@/components/ui/checkbox';
import { getIconColor } from '@/utils/colorUtils';
const List: FC = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const [nextCursor, setNextCursor] = useState<string | undefined>(undefined);
const [previousCursor, setPreviousCursor] = useState<string | undefined>(undefined);
const [filters, setFilters] = useState<FilterValues>({});
const emailActions = useEmailActions();
const [openSummarizeModal, setOpenSummarizeModal] = useState(false);
const [showModal, setShowModal] = useState(false);
const { mutate: summarizeEmail, isPending: isSummarizing, data: summarizeData } = useSummarizeEmail();
const { mutate: markAllRead, isPending: isMarkingAllRead } = useMarkAllRead();
const { data: inboxData, isLoading, refetch, isFetching } = useGetInbox({
next: nextCursor,
previous: previousCursor,
limit: 10,
...filters
});
// تابع برای گرفتن فقط ساعت
const formatTime = (dateString: string) => {
try {
if (!dateString) return '--:--';
const date = new Date(dateString);
if (isNaN(date.getTime())) return '--:--';
return new Intl.DateTimeFormat("fa-IR", {
hour: "2-digit",
minute: "2-digit",
}).format(date);
} catch (error) {
console.error('خطا در فرمت زمان:', error);
return '--:--';
}
};
const [selectedMessages, setSelectedMessages] = useState<InboxMessage[]>([]);
const [selectedMessage, setSelectedMessage] = useState<InboxMessage | null>(null);
const [selectAll, setSelectAll] = useState(false);
// ستون‌های دسکتاپ (مثل اولیه)
const desktopColumns: ColumnType<InboxMessage>[] = [
{
key: 'subject',
title: 'عنوان پیام',
render: (item) => (
<div className="truncate max-w-[250px]">{item.subject || 'بدون موضوع'}</div>
)
},
{
key: 'intro',
title: 'پیش نمایش',
render: (item) => (
<div className="truncate max-w-[250px]">{item.intro || 'بدون موضوع'}</div>
)
},
{
key: 'from',
title: 'فرستنده',
render: (item) => (
<div className="flex items-center">
<span className="truncate">{item.from.name || item.from.address}</span>
</div>
)
},
{
key: 'date',
title: 'تاریخ',
align: 'center',
render: (item) => (
<span>{formatTime(item.date)}</span>
)
},
];
// ستون‌های موبایل (سه خطی)
const mobileColumns: ColumnType<InboxMessage>[] = [
{
key: 'content',
title: 'محتوا',
render: (item) => (
<div className="flex flex-col gap-1 w-full">
<div className="flex items-center gap-2 w-full">
<span className="truncate font-medium text-sm flex-1 min-w-0">
{item.from.name || item.from.address}
</span>
<span className="text-[10px] text-gray-500 whitespace-nowrap">
{formatTime(item.date)}
</span>
</div>
<div className="truncate w-full text-sm font-medium">
{item.subject}
</div>
<div className="truncate w-full text-xs text-gray-600">
{item.intro}
</div>
</div>
)
}
];
// انتخاب ستون‌ها بر اساس سایز صفحه
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
React.useEffect(() => {
const handleResize = () => {
setIsMobile(window.innerWidth < 768);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const columns = isMobile ? mobileColumns : desktopColumns;
const handleSelectAll = (checked: boolean) => {
setSelectAll(checked);
if (checked) {
const messages = inboxData?.data?.results || [];
setSelectedMessages(messages);
} else {
setSelectedMessages([]);
}
};
const tableActions = (
<div className='flex items-center gap-4 md:gap-4'>
<div className="flex items-center gap-2">
<Checkbox
checked={selectAll}
onCheckedChange={handleSelectAll}
className="w-4 h-4"
/>
</div>
<Refresh2
size={18}
color={getIconColor('primary')}
className={`cursor-pointer min-w-[18px] ${isFetching ? 'animate-spin-reverse' : ''}`}
onClick={() => refetch()}
/>
<RowActionsDropdown actions={[
{
label: 'خواندن همه',
icon: <img src={MarkAsRead} className='w-[18px] filterWhite' />,
onClick: () => handleMarkAll()
}
]} />
</div>
);
const selectedActions = (
<>
<img
src={MarkAsRead}
className='w-[18px] cursor-pointer filterWhite'
onClick={() => handleMarkAsReadSelected()}
/>
<ArchiveTick
color={getIconColor('primary')}
onClick={() => handleArchiveSelected()}
className="cursor-pointer lg:size-[18px] size-[20px]"
/>
<InfoCircle color={getIconColor('primary')} className="cursor-pointer lg:size-[18px] size-[20px]" />
<Star1
color={getIconColor('primary')}
className="cursor-pointer lg:size-[18px] size-[20px]"
onClick={() => handleFavoriteSelected()}
/>
<Trash
color='#CD0000'
onClick={() => handleDeleteSelected()}
className="cursor-pointer lg:size-[18px] size-[20px]"
/>
</>
);
const handleFilterChange = (newFilters: FilterValues) => {
setFilters(newFilters);
setNextCursor(undefined);
setPreviousCursor(undefined);
};
const handleSelectionChange = (selectedRows: InboxMessage[]) => {
setSelectedMessages(selectedRows);
// تنظیم وضعیت select all بر اساس انتخاب شده‌ها
const messages = inboxData?.data?.results || [];
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
};
const handleDeleteSelected = () => {
const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMoveToTrash(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]);
setSelectAll(false);
};
const handleArchiveSelected = () => {
const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkArchive(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]);
setSelectAll(false);
};
const handleMarkAsReadSelected = () => {
const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMarkAsSeen(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]);
setSelectAll(false);
};
const handleFavoriteSelected = () => {
const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkFavorite(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]);
setSelectAll(false);
};
const handleMarkAll = () => {
markAllRead(undefined, {
onSuccess: () => {
refetch();
setShowModal(false);
},
onError: (error: ErrorType) => {
toast(error.response?.data?.error.message[0], 'error')
}
});
};
const getInlineActions = (item: InboxMessage) => [
{
icon: <Favorite flagged={item.flagged} id={item.id.toString()} mailbox={item.mailbox} />,
onClick: () => { }, // عملیات در کامپوننت Favorite خودش انجام می‌شود
tooltip: item.flagged ? 'حذف از علاقه‌مندی‌ها' : 'اضافه به علاقه‌مندی‌ها'
},
{
icon: <Summerize item={item} id={item.id.toString()} mailbox={item.mailbox} />,
onClick: () => { }, // عملیات در کامپوننت Summerize خودش انجام می‌شود
tooltip: 'خلاصه‌سازی'
}
];
const getRowActions = (message: InboxMessage): RowActionItem[] => [
{
label: 'مشاهده خلاصه',
icon: <img src={Intelligense} alt='summerize' className='w-[18px] ' />,
onClick: () => {
setSelectedMessage(message)
setOpenSummarizeModal(true)
summarizeEmail({ messageId: message.id, mailbox: message.mailbox })
},
},
];
const handleNext = (cursor?: string) => {
setNextCursor(cursor);
setPreviousCursor(undefined);
};
const handlePrevious = (cursor?: string) => {
setPreviousCursor(cursor);
setNextCursor(undefined);
};
const messages = inboxData?.data?.results || [];
const rootData = inboxData?.data as unknown as Record<string, unknown>;
const responseNextCursor = (typeof rootData?.nextCursor === 'string' && rootData.nextCursor !== '') ? rootData.nextCursor as string : undefined;
const responsePreviousCursor = (typeof rootData?.previousCursor === 'string' && rootData.previousCursor !== '') ? rootData.previousCursor as string : undefined;
return (
<div className='mt-2 md:mt-4 px-2 md:px-0'>
<div className='flex justify-between items-center'>
<h1 className="">{t('received.title')}</h1>
</div>
<Filters
fields={[
{ type: 'date', name: 'datestart', placeholder: t('received.from_date') },
{ type: 'date', name: 'dateend', placeholder: t('received.to_date') },
{
type: 'select',
name: 'unseen',
placeholder: t('received.all'),
options: [
{ value: '', label: t('received.all') },
{ value: 'false', label: t('received.read') },
{ value: 'true', label: t('received.unread') }
]
},
{ type: 'input', name: 'search', placeholder: t('received.search') }
]}
onChange={handleFilterChange}
searchField="search"
/>
<Table<InboxMessage>
columns={columns}
data={messages}
isLoading={isLoading || emailActions.isLoading}
showHeader={false}
actions={tableActions}
selectedActions={selectedActions}
onSelectionChange={handleSelectionChange}
selectedRows={selectedMessages}
onRowClick={(id, item) => navigate(`/mail/${id}/${item.mailbox}`)}
noDataMessage={
<div className="flex flex-col items-center">
<InfoCircle size={32} className="text-gray-300 mb-2" />
<div className="text-sm">هیچ پیامی یافت نشد</div>
</div>
}
selectable={true}
inlineActions={getInlineActions}
rowActions={getRowActions}
pagination={(responseNextCursor || responsePreviousCursor) ? {
hasNext: !!responseNextCursor,
nextCursor: responseNextCursor,
previousCursor: responsePreviousCursor,
onNext: handleNext,
onPrevious: handlePrevious
} : undefined}
/>
{selectedMessage && (
<SummarizeModal
open={openSummarizeModal}
onClose={() => setOpenSummarizeModal(false)}
isPending={isSummarizing}
item={selectedMessage}
id={selectedMessage.id.toString()}
mailbox={selectedMessage.mailbox}
data={summarizeData}
/>
)}
<ModalConfrim
isOpen={showModal}
close={() => setShowModal(false)}
onConfrim={handleMarkAll}
isLoading={isMarkingAllRead}
title={'خواندن همه'}
label={'آیا از خواندن همه پیام ها اطمینان دارید؟'}
/>
</div>
)
}
export default List