359 lines
14 KiB
TypeScript
359 lines
14 KiB
TypeScript
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, Sms } from 'iconsax-react';
|
||
import { 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 Button from '@/components/Button';
|
||
import ModalConfrim from '@/components/ModalConfrim';
|
||
import { ErrorType } from '@/helpers/types';
|
||
import { toast } from '@/components/Toast';
|
||
|
||
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 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-xs 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 tableActions = (
|
||
<div className='flex items-center gap-2 md:gap-4'>
|
||
<Refresh2
|
||
size={18}
|
||
color='black'
|
||
className={`cursor-pointer min-w-[18px] ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||
onClick={() => refetch()}
|
||
/>
|
||
</div>
|
||
);
|
||
|
||
const selectedActions = (
|
||
<>
|
||
<img
|
||
src={MarkAsRead}
|
||
className='w-[18px] cursor-pointer'
|
||
onClick={() => handleMarkAsReadSelected()}
|
||
/>
|
||
<ArchiveTick
|
||
color='black'
|
||
onClick={() => handleArchiveSelected()}
|
||
className="cursor-pointer lg:size-[18px] size-[20px]"
|
||
|
||
/>
|
||
<InfoCircle color='black' className="cursor-pointer lg:size-[18px] size-[20px]" />
|
||
<Star1
|
||
color='black'
|
||
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);
|
||
};
|
||
|
||
const handleDeleteSelected = () => {
|
||
const messageIds = selectedMessages.map(message => message.id);
|
||
emailActions.bulkMoveToTrash(messageIds, selectedMessages[0].mailbox);
|
||
setSelectedMessages([]);
|
||
};
|
||
|
||
const handleArchiveSelected = () => {
|
||
const messageIds = selectedMessages.map(message => message.id);
|
||
emailActions.bulkArchive(messageIds, selectedMessages[0].mailbox);
|
||
setSelectedMessages([]);
|
||
};
|
||
|
||
const handleMarkAsReadSelected = () => {
|
||
const messageIds = selectedMessages.map(message => message.id);
|
||
emailActions.bulkMarkAsSeen(messageIds, selectedMessages[0].mailbox);
|
||
setSelectedMessages([]);
|
||
};
|
||
|
||
const handleFavoriteSelected = () => {
|
||
const messageIds = selectedMessages.map(message => message.id);
|
||
emailActions.bulkFavorite(messageIds, selectedMessages[0].mailbox);
|
||
setSelectedMessages([]);
|
||
};
|
||
|
||
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>
|
||
<Button
|
||
onClick={() => setShowModal(true)}
|
||
loading={isMarkingAllRead}
|
||
className='w-fit'
|
||
>
|
||
<div className='flex items-center gap-2'>
|
||
<Sms size={18} color='white' />
|
||
<span>خواندن همه</span>
|
||
</div>
|
||
</Button>
|
||
</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
|