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 -44
View File
@@ -1,105 +1,190 @@
import Filters, { FilterValues } from '../../components/Filters';
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Table from '../../components/Table';
import { ColumnType } from '../../components/types/TableTypes';
import { InfoCircle, More, Refresh2 } from 'iconsax-react';
interface Message extends Record<string, unknown> {
id: string;
title: string;
sender: string;
date: string;
read: boolean;
}
import { InfoCircle, More, Refresh2, AttachSquare, Trash, Archive, Star } from 'iconsax-react';
import { useGetSentMessages } from './hooks/useSentData';
import { InboxMessage } from './types/SentTypes';
import { useNavigate } from 'react-router-dom';
import { Paths } from '../../utils/Paths';
import { useEmailActions } from '@/hooks/useEmailActions';
import { RowActionItem } from '../../components/RowActionsDropdown';
import { formatDate } from '@/config/func';
const List: FC = () => {
const { t } = useTranslation();
const [isLoading] = useState(false);
const navigate = useNavigate();
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState<FilterValues>({});
const emailActions = useEmailActions();
const messageData: Message[] = [
{ id: '1', title: 'اولین پیام', sender: 'علی محمدی', date: '1402/05/12', read: true },
{ id: '2', title: 'دومین پیام', sender: 'زهرا احمدی', date: '1402/05/13', read: true },
{ id: '3', title: 'جلسه هفتگی', sender: 'مدیریت', date: '1402/05/14', read: true },
];
const { data: sentData, isLoading, refetch, isFetching } = useGetSentMessages({
page: currentPage,
limit: 10,
...filters
});
const columns: ColumnType<Message>[] = [
const [selectedMessages, setSelectedMessages] = useState<InboxMessage[]>([]);
const columns: ColumnType<InboxMessage>[] = [
{
key: 'title',
title: 'عنوان پیام',
key: 'subject',
title: 'موضوع پیام',
render: (item) => (
<div className="flex items-center">
{!item.read && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 mr-2" />}
<span className="truncate">{item.title}</span>
<div className="flex items-center gap-2">
<span className="truncate">{item.subject}</span>
{item.attachments && (
<AttachSquare size={16} className="text-gray-500 flex-shrink-0" />
)}
</div>
)
},
{
key: 'sender',
title: 'فرستنده',
key: 'to',
title: 'گیرنده',
render: (item) => (
<span className="truncate">{item.sender}</span>
<div className="flex items-center">
{!item.seen && <div className="w-1.5 h-1.5 md:w-2 md:h-2 rounded-full bg-blue-500 ml-2" />}
<span className="truncate">
{item.to.map(recipient => recipient.address).join(', ')}
</span>
</div>
)
},
{
key: 'date',
title: 'تاریخ',
title: 'تاریخ ارسال',
align: 'center',
render: (item) => (
<span className="text-xs md:text-sm">{item.date}</span>
<span className="text-xs md:text-sm">
{formatDate(item.date)}
</span>
)
},
];
const tableActions = (
<div className='flex items-center gap-2 md:gap-4'>
<Refresh2 size={18} color='black' className="cursor-pointer" />
<Refresh2
size={18}
color='black'
className={`cursor-pointer ${isFetching ? 'animate-spin-reverse' : ''}`}
onClick={() => refetch()}
/>
<More size={18} color='black' className='rotate-90 cursor-pointer' />
</div>
);
const selectedActions = (
<>
<Star size={18} color='black' onClick={() => handleFavoriteSelected()} className="cursor-pointer" />
<Archive size={18} color='black' onClick={() => handleArchiveSelected()} className="cursor-pointer" />
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
</>
);
const handleFilterChange = (newFilters: FilterValues) => {
console.log('Applied filters:', newFilters);
setFilters(newFilters);
setCurrentPage(1);
};
const handleRowClick = (id: number) => {
navigate(`${Paths.detailEmail}${id}`);
};
const handleSelectionChange = (selectedRows: InboxMessage[]) => {
setSelectedMessages(selectedRows);
};
const handleDeleteSelected = () => {
const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMoveToTrash(messageIds);
setSelectedMessages([]);
};
const handleArchiveSelected = () => {
const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkArchive(messageIds);
setSelectedMessages([]);
};
const handleFavoriteSelected = () => {
const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkFavorite(messageIds);
setSelectedMessages([]);
};
const getRowActions = (message: InboxMessage): RowActionItem[] => [
{
label: 'اضافه به علاقه‌مندی‌ها',
icon: <Star size={16} color="black" />,
onClick: () => emailActions.favorite(message.id),
},
{
label: 'بایگانی',
icon: <Archive size={16} color="black" />,
onClick: () => emailActions.archive(message.id),
},
{
label: 'حذف',
icon: <Trash size={16} color="red" />,
onClick: () => emailActions.moveToTrash(message.id),
},
];
const handlePageChange = (page: number) => {
setCurrentPage(page);
};
const messages = sentData?.data?.sentMails || [];
const pager = sentData?.data?.pager;
return (
<div className='mt-2 md:mt-4 px-2 md:px-0'>
<h1 className="text-lg mb-4 md:mb-0">{t('sent.title')}</h1>
<h1 className="text-lg mb-4 md:mb-0">ارسال شده</h1>
<Filters
fields={[
{ type: 'date', name: 'from_date', placeholder: t('sent.from_date') },
{ type: 'date', name: 'to_date', placeholder: t('sent.to_date') },
{ type: 'date', name: 'dateFrom', placeholder: 'از تاریخ' },
{ type: 'date', name: 'dateTo', placeholder: 'تا تاریخ' },
{
type: 'select',
name: 'status',
placeholder: t('sent.all'),
name: 'isRead',
placeholder: 'همه',
options: [
{ value: 'all', label: t('sent.all') },
{ value: 'read', label: t('sent.read') },
{ value: 'unread', label: t('sent.unread') }
{ value: '', label: 'همه' },
{ value: 'true', label: 'خوانده شده' },
{ value: 'false', label: 'خوانده نشده' }
]
},
{ type: 'input', name: 'search', placeholder: t('sent.search') }
{ type: 'input', name: 'search', placeholder: 'جستجو...' }
]}
onChange={handleFilterChange}
searchField="search"
/>
<Table<Message>
<Table<InboxMessage>
columns={columns}
data={messageData}
isLoading={isLoading}
data={messages}
isLoading={isLoading || emailActions.isLoading}
showHeader={false}
actions={tableActions}
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
selectedActions={selectedActions}
onSelectionChange={handleSelectionChange}
selectedRows={selectedMessages}
onRowClick={handleRowClick}
noDataMessage={
<div className="flex flex-col items-center py-6">
<div className="flex flex-col items-center">
<InfoCircle size={32} className="text-gray-300 mb-2" />
<div className="text-sm">هیچ پیامی یافت نشد</div>
</div>
}
selectable={true}
rowActions={getRowActions}
pagination={pager ? {
totalPages: pager.totalPages,
currentPage: pager.page,
onPageChange: handlePageChange
} : undefined}
/>
</div>
+11
View File
@@ -0,0 +1,11 @@
import { useQuery } from '@tanstack/react-query';
import { getSentMessages } from '../service/SentService';
import { MessageListQueryDto } from '../types/SentTypes';
export const useGetSentMessages = (query: MessageListQueryDto) => {
return useQuery({
queryKey: ['sent-messages', query],
queryFn: () => getSentMessages(query),
staleTime: 5 * 60 * 1000, // 5 minutes
});
};
+28
View File
@@ -0,0 +1,28 @@
import axios from "../../../config/axios";
import {
MessageListQueryDto,
InboxResponse,
MessageDetail,
MessageDetailResponse,
} from "../types/SentTypes";
export const getSentMessages = async (
query: MessageListQueryDto
): Promise<InboxResponse> => {
const { data } = await axios.get(`/email/messages/sent`, { params: query });
return data;
};
export const getSentMessageDetail = async (
messageId: number
): Promise<MessageDetail> => {
const { data }: { data: MessageDetailResponse } = await axios.get(
`/email/messages/sent/${messageId}`
);
return data.data.message;
};
export const deleteSentMessage = async (messageId: number): Promise<void> => {
const { data } = await axios.delete(`/email/messages/sent/${messageId}`);
return data;
};
+76
View File
@@ -0,0 +1,76 @@
export interface MessageListQueryDto {
page?: number;
limit?: number;
search?: string;
from_date?: string;
to_date?: string;
status?: "all" | "read" | "unread";
}
export interface EmailAddress {
address: string;
name: string;
}
export interface ContentType {
value: string;
params: {
boundary: string;
};
}
export interface InboxMessage extends Record<string, unknown> {
id: number;
mailbox: string;
thread: string;
from: EmailAddress;
to: EmailAddress[];
cc: EmailAddress[];
bcc: EmailAddress[];
messageId: string;
subject: string;
date: string;
idate: string;
intro: string;
attachments: boolean;
attachmentsList: unknown[];
size: number;
seen: boolean;
deleted: boolean;
flagged: boolean;
draft: boolean;
answered: boolean;
forwarded: boolean;
references: unknown[];
contentType: ContentType;
}
export interface Pager {
page: number;
limit: number;
totalItems: number;
totalPages: number;
prevPage: boolean;
nextPage: boolean;
}
export interface InboxResponse {
statusCode: number;
success: boolean;
data: {
pager: Pager;
sentMails: InboxMessage[];
};
}
export interface MessageDetail extends InboxMessage {
body: string;
}
export interface MessageDetailResponse {
statusCode: number;
success: boolean;
data: {
message: MessageDetail;
};
}