recive email + save draft and list draft + skeleton email detail

This commit is contained in:
hamid zarghami
2025-07-10 11:06:36 +03:30
parent 4258f57662
commit df6e74c632
14 changed files with 540 additions and 228 deletions
+98 -43
View File
@@ -5,42 +5,55 @@ import Table from '../../components/Table';
import { ColumnType } from '../../components/types/TableTypes';
import { InfoCircle, More, Refresh2, Trash, Edit } from 'iconsax-react';
import { RowActionItem } from '../../components/RowActionsDropdown';
interface Message extends Record<string, unknown> {
id: string;
title: string;
sender: string;
date: string;
read: boolean;
}
import { useNavigate } from 'react-router-dom';
import { useGetDrafts, useDeleteDraft } from './hooks/useDraftData';
import { DraftMessage } from './types/DraftTypes';
import { formatDate } from '@/config/func';
const List: FC = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const [isLoading] = useState(false);
const [selectedMessages, setSelectedMessages] = useState<Message[]>([]);
const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState<FilterValues>({});
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: draftData, isLoading, refetch, isFetching } = useGetDrafts({
page: currentPage,
limit: 10,
...filters
});
const columns: ColumnType<Message>[] = [
const deleteMutation = useDeleteDraft();
const [selectedMessages, setSelectedMessages] = useState<DraftMessage[]>([]);
const columns: ColumnType<DraftMessage>[] = [
{
key: 'title',
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>
title: 'title',
render: () => (
<div className="text-[#0038FF]">
پیش نویس
</div>
)
},
{
key: 'sender',
title: 'فرستنده',
key: 'subject',
title: 'عنوان پیام',
render: (item) => (
<span className="truncate">{item.sender}</span>
<div className="flex items-center">
<span className="truncate">{item.subject || 'بدون موضوع'}</span>
</div>
)
},
{
key: 'to',
title: 'گیرنده',
render: (item) => (
<span className="truncate">
{item.to && item.to.length > 0
? (item.to[0].name || item.to[0].address)
: 'بدون گیرنده'
}
</span>
)
},
{
@@ -48,87 +61,129 @@ const List: FC = () => {
title: 'تاریخ',
align: 'center',
render: (item) => (
<span className="text-xs md:text-sm">{item.date}</span>
<span>{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 = (
<>
<Trash size={18} color='black' onClick={() => handleDeleteSelected()} className="cursor-pointer" />
<Edit size={18} color='black' onClick={() => handleEditSelected()} className="cursor-pointer" />
<Trash
size={18}
color='black'
onClick={() => handleDeleteSelected()}
className="cursor-pointer"
/>
<Edit
size={18}
color='black'
onClick={() => handleEditSelected()}
className="cursor-pointer"
/>
</>
);
const handleFilterChange = (newFilters: FilterValues) => {
console.log('Applied filters:', newFilters);
setFilters(newFilters);
setCurrentPage(1); // Reset to first page when filters change
};
const handleSelectionChange = (selectedRows: Message[]) => {
const handleSelectionChange = (selectedRows: DraftMessage[]) => {
setSelectedMessages(selectedRows);
};
const handleDeleteSelected = () => {
console.log('حذف پیام‌های انتخاب شده:', selectedMessages.map(m => m.id));
// اینجا منطق حذف را پیاده‌سازی کنید
selectedMessages.forEach(message => {
deleteMutation.mutate(message.id.toString());
});
setSelectedMessages([]);
};
const handleEditSelected = () => {
console.log('ویرایش پیام‌های انتخاب شده:', selectedMessages.map(m => m.id));
// اینجا منطق ویرایش را پیاده‌سازی کنید
if (selectedMessages.length === 1) {
// Navigate to edit page for single draft
navigate(`/draft/edit/${selectedMessages[0].id}`);
} else {
console.log('ویرایش چندین پیش‌نویس انتخاب شده:', selectedMessages.map(m => m.id));
}
};
const getRowActions = (message: Message): RowActionItem[] => [
const handleDeleteDraft = (messageId: number) => {
deleteMutation.mutate(messageId.toString());
};
const handleEditDraft = (messageId: number) => {
navigate(`/draft/edit/${messageId}`);
};
const getRowActions = (message: DraftMessage): RowActionItem[] => [
{
label: 'ویرایش',
icon: <Edit size={16} color="black" />,
onClick: () => console.log('ویرایش پیام', message.id),
onClick: () => handleEditDraft(message.id),
},
{
label: 'حذف',
icon: <Trash size={16} color="red" />,
onClick: () => console.log('حذف پیام', message.id),
onClick: () => handleDeleteDraft(message.id),
},
];
const handlePageChange = (page: number) => {
setCurrentPage(page);
};
const messages = draftData?.data?.draftsMail || [];
const pager = draftData?.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('draft.title')}</h1>
<Filters
fields={[
{ type: 'date', name: 'from_date', placeholder: t('draft.from_date') },
{ type: 'date', name: 'to_date', placeholder: t('draft.to_date') },
{ type: 'date', name: 'dateFrom', placeholder: t('draft.from_date') },
{ type: 'date', name: 'dateTo', placeholder: t('draft.to_date') },
{ type: 'input', name: 'search', placeholder: t('draft.search') }
]}
onChange={handleFilterChange}
searchField="search"
/>
<Table<Message>
<Table<DraftMessage>
columns={columns}
data={messageData}
data={messages}
isLoading={isLoading}
showHeader={false}
actions={tableActions}
selectedActions={selectedActions}
onSelectionChange={handleSelectionChange}
onRowClick={(id) => console.log(`کلیک روی پیام با شناسه ${id}`)}
onRowClick={(id) => navigate(`/draft/edit/${id}`)}
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>
)
+45
View File
@@ -0,0 +1,45 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import * as api from '../service/DraftService';
import { MessageListQueryDto, DraftUpdateDto } from '../types/DraftTypes';
export const useGetDrafts = (query: MessageListQueryDto) => {
return useQuery({
queryKey: ['drafts', query],
queryFn: () => api.getDrafts(query),
staleTime: 1000 * 60 * 5, // 5 minutes
});
};
export const useGetDraftDetail = (messageId: string, enabled: boolean = true) => {
return useQuery({
queryKey: ['draft', messageId],
queryFn: () => api.getDraftDetail(messageId),
enabled: !!messageId && enabled,
staleTime: 1000 * 60 * 10, // 10 minutes
});
};
export const useDeleteDraft = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (messageId: string) => api.deleteDraft(messageId),
onSuccess: () => {
// Invalidate drafts queries to refresh the list
queryClient.invalidateQueries({ queryKey: ['drafts'] });
},
});
};
export const useEditDraft = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ messageId, draftData }: { messageId: string; draftData: DraftUpdateDto }) =>
api.editDraft(messageId, draftData),
onSuccess: () => {
// Invalidate drafts queries to refresh the list
queryClient.invalidateQueries({ queryKey: ['drafts'] });
},
});
};
+37
View File
@@ -0,0 +1,37 @@
import axios from "../../../config/axios";
import {
MessageListQueryDto,
InboxResponse,
InboxMessage,
DraftUpdateDto,
} from "../types/DraftTypes";
export const getDrafts = async (
query: MessageListQueryDto
): Promise<InboxResponse> => {
const { data } = await axios.get(`/email/messages/drafts`, { params: query });
return data;
};
export const deleteDraft = async (messageId: string): Promise<void> => {
const { data } = await axios.delete(`/email/messages/drafts/${messageId}`);
return data;
};
export const editDraft = async (
messageId: string,
draftData: DraftUpdateDto
): Promise<void> => {
const { data } = await axios.put(
`/email/messages/drafts/${messageId}`,
draftData
);
return data;
};
export const getDraftDetail = async (
messageId: string
): Promise<InboxMessage> => {
const { data } = await axios.get(`/email/messages/drafts/${messageId}`);
return data.data.message;
};
+107
View File
@@ -0,0 +1,107 @@
export interface EmailRecipientDto {
name?: string;
address: string;
}
export interface EmailHeaderDto {
key: string;
value: string;
}
export interface EmailAttachmentDto {
filename?: string;
contentType?: string;
encoding?: string;
contentTransferEncoding?: string;
contentDisposition?: "inline" | "attachment";
content: string;
cid?: string;
}
export interface MessageListQueryDto {
page?: number;
limit?: number;
order?: string;
search?: string;
from?: string;
subject?: string;
dateFrom?: string;
dateTo?: string;
hasAttachment?: boolean;
isRead?: boolean;
isFlagged?: boolean;
}
export interface DraftMessage extends Record<string, unknown> {
id: number;
mailbox: string;
thread: string;
from: {
address: string;
name: string;
};
to: Array<{
address: string;
name: string;
}>;
cc: Array<{
address: string;
name: string;
}>;
bcc: Array<{
address: string;
name: string;
}>;
messageId: string;
subject: string;
date: string;
idate: string;
intro: string;
attachments: boolean;
attachmentsList: EmailAttachmentDto[];
size: number;
seen: boolean;
deleted: boolean;
flagged: boolean;
draft: boolean;
answered: boolean;
forwarded: boolean;
references: string[];
contentType: {
value: string;
params: {
boundary: string;
};
};
}
export type InboxMessage = DraftMessage;
export interface InboxPager {
page: number;
limit: number;
totalItems: number;
totalPages: number;
prevPage: boolean;
nextPage: boolean;
}
export interface InboxResponse {
statusCode: number;
success: boolean;
data: {
pager: InboxPager;
draftsMail: DraftMessage[];
};
}
export interface DraftUpdateDto {
from?: EmailRecipientDto;
to?: EmailRecipientDto[];
cc?: EmailRecipientDto[];
bcc?: EmailRecipientDto[];
subject?: string;
text?: string;
html?: string;
attachments?: EmailAttachmentDto[];
}