Files
dmail-front/src/pages/received/List.tsx
T
hamid zarghami 8b28a2dee0 inbox 3 row
2025-07-28 16:38:27 +03:30

359 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, 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