change all actions

This commit is contained in:
hamid zarghami
2025-07-20 16:47:01 +03:30
parent 1106952e22
commit 9eed4ede94
22 changed files with 309 additions and 127 deletions
+102 -17
View File
@@ -3,8 +3,9 @@ import { CloseCircle } from 'iconsax-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useSharedStore } from '@/shared/store/sharedStore' import { useSharedStore } from '@/shared/store/sharedStore'
import { useAuthStore } from '../../pages/auth/store/AuthStore' import { useAuthStore } from '../../pages/auth/store/AuthStore'
import { useSendEmail } from '../../pages/received/hooks/useEmailData' import { useGetMessageDetail, useSendEmail } from '../../pages/received/hooks/useEmailData'
import { SendEmailDto } from '../../pages/received/types/Types' import { SendEmailDto } from '../../pages/received/types/Types'
import { useUpdateDraft, useSendDraft } from '../../pages/draft/hooks/useDraftData'
import Input from '../Input' import Input from '../Input'
import ReactQuill from 'react-quill-new'; import ReactQuill from 'react-quill-new';
import Button from '../Button' import Button from '../Button'
@@ -16,8 +17,8 @@ import { toast } from '../Toast'
const NewMessage: FC = () => { const NewMessage: FC = () => {
const { t } = useTranslation('global') const { t } = useTranslation('global')
const [value, setValue] = useState(''); const [value, setValue] = useState<string>('');
const { setOpenNewMessage, openNewMessage } = useSharedStore() const { setOpenNewMessage, openNewMessage, draftData, setDraftData, editingDraftId, setEditingDraftId } = useSharedStore()
const { email: userEmail } = useAuthStore() const { email: userEmail } = useAuthStore()
const [isClosing, setIsClosing] = useState(false) const [isClosing, setIsClosing] = useState(false)
const [shouldRender, setShouldRender] = useState(false) const [shouldRender, setShouldRender] = useState(false)
@@ -38,6 +39,33 @@ const NewMessage: FC = () => {
// API hooks // API hooks
const sendEmailMutation = useSendEmail() const sendEmailMutation = useSendEmail()
const updateDraftMutation = useUpdateDraft()
const sendDraftMutation = useSendDraft()
// Get draft detail if draftData exists
const { data: draftDetail } = useGetMessageDetail(
draftData?.id.toString() || '',
draftData?.mailbox || '',
!!draftData?.id
)
// Load draft data into form when draft detail is available
useEffect(() => {
if (draftDetail) {
// Extract emails from to array
const toEmailsList = draftDetail.to?.map(recipient => recipient.address) || []
setToEmails(toEmailsList)
setSubject(draftDetail.subject || '')
// Try to load HTML content if available, fallback to text or intro
const draftWithContent = draftDetail as unknown as { html?: string; text?: string; intro: string }
const content = ensureString(draftWithContent.html || draftWithContent.text || draftWithContent.intro)
setValue(content)
// Clear draft data after loading
setDraftData(null)
}
}, [draftDetail, setDraftData])
useEffect(() => { useEffect(() => {
if (openNewMessage) { if (openNewMessage) {
@@ -62,6 +90,9 @@ const NewMessage: FC = () => {
setIsClosing(true) setIsClosing(true)
setTimeout(() => { setTimeout(() => {
setOpenNewMessage(false) setOpenNewMessage(false)
resetForm()
setDraftData(null)
setEditingDraftId(null)
}, 300) }, 300)
} }
@@ -76,6 +107,12 @@ const NewMessage: FC = () => {
return emailRegex.test(email) return emailRegex.test(email)
} }
const ensureString = (val: unknown): string => {
if (typeof val === 'string') return val
if (val === null || val === undefined) return ''
return String(val)
}
const addEmailToList = (email: string) => { const addEmailToList = (email: string) => {
if (!isValidEmail(email)) { if (!isValidEmail(email)) {
return return
@@ -156,25 +193,18 @@ const NewMessage: FC = () => {
} }
const handleEmailAction = async (isDraft: boolean = false) => { const handleEmailAction = async (isDraft: boolean = false) => {
const isEditingDraft = !!editingDraftId
if (!isDraft && (toEmails.length === 0 || !subject)) { if (!isDraft && (toEmails.length === 0 || !subject)) {
toast('لطفا تمام فیلدهای مورد نیاز را پر کنید', 'error') toast('لطفا تمام فیلدهای مورد نیاز را پر کنید', 'error')
return return
} }
if (isDraft && toEmails.length === 0 && !subject && !value) { if (isDraft && toEmails.length === 0 && !subject && ensureString(value).trim() === '') {
toast('حداقل یکی از فیلدها باید پر باشد', 'error') toast('حداقل یکی از فیلدها باید پر باشد', 'error')
return return
} }
const emailData: SendEmailDto = {
from: { address: userEmail || 'sender@example.com' },
to: parseEmailAddresses(toEmails),
subject,
html: value,
text: value.replace(/<[^>]*>/g, ''), // Strip HTML for text version
...(isDraft && { isDraft: true, uploadOnly: true }),
}
// Set appropriate loading state // Set appropriate loading state
if (isDraft) { if (isDraft) {
setIsSavingDraft(true) setIsSavingDraft(true)
@@ -182,18 +212,73 @@ const NewMessage: FC = () => {
setIsSending(true) setIsSending(true)
} }
try {
let messageId = ''
if (isEditingDraft) {
// We're editing an existing draft
const draftUpdateData = {
to: parseEmailAddresses(toEmails),
subject,
html: ensureString(value),
text: ensureString(value).replace(/<[^>]*>/g, ''),
}
if (isDraft) {
// Just update the draft
await updateDraftMutation.mutateAsync({
messageId: editingDraftId.toString(),
draftData: draftUpdateData
}, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onSuccess: (data: any) => {
messageId = data.data?.messageId
}
})
} else {
// Update first, then send
await updateDraftMutation.mutateAsync({
messageId: editingDraftId.toString(),
draftData: {
...draftUpdateData,
isDraft: true,
uploadOnly: true
}
}, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onSuccess: (data: any) => {
messageId = data.data?.messageId
}
})
await sendDraftMutation.mutateAsync(messageId || editingDraftId.toString())
toast('پیام با موفقیت ارسال شد', 'success')
}
handleClose()
} else {
// Creating new email/draft
const emailData: SendEmailDto = {
from: { address: userEmail || 'sender@example.com' },
to: parseEmailAddresses(toEmails),
subject,
html: ensureString(value),
text: ensureString(value).replace(/<[^>]*>/g, ''),
...(isDraft && { isDraft: true, uploadOnly: true }),
}
await sendEmailMutation.mutateAsync(emailData, { await sendEmailMutation.mutateAsync(emailData, {
onSuccess: (data) => { onSuccess: (data) => {
toast(data.message, 'success') toast(data.message, 'success')
handleClose() handleClose()
resetForm() }
}, })
onSettled: () => { }
} catch (error) {
console.error('Error in email action:', error)
toast('خطا در انجام عملیات', 'error')
} finally {
// Reset loading states // Reset loading states
setIsSending(false) setIsSending(false)
setIsSavingDraft(false) setIsSavingDraft(false)
} }
})
} }
const handleSend = () => handleEmailAction(false) const handleSend = () => handleEmailAction(false)
+29 -29
View File
@@ -50,7 +50,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const markAsSeenMutation = useMutation({ const markAsSeenMutation = useMutation({
mutationFn: markMessageAsSeen, mutationFn: ({ messageId, mailbox }: { messageId: number; mailbox: string }) => markMessageAsSeen(messageId, mailbox),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیام به عنوان خوانده شده علامت‌گذاری شد', 'success'); toast(data?.data?.message || 'پیام به عنوان خوانده شده علامت‌گذاری شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -58,7 +58,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const archiveMutation = useMutation({ const archiveMutation = useMutation({
mutationFn: moveMessageToArchive, mutationFn: ({ messageId, mailbox }: { messageId: number; mailbox: string }) => moveMessageToArchive(messageId, mailbox),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیام به آرشیو منتقل شد', 'success'); toast(data?.data?.message || 'پیام به آرشیو منتقل شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -66,7 +66,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const favoriteMutation = useMutation({ const favoriteMutation = useMutation({
mutationFn: moveMessageToFavorite, mutationFn: ({ messageId, mailbox }: { messageId: number; mailbox: string }) => moveMessageToFavorite(messageId, mailbox),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیام به علاقه‌مندی‌ها اضافه شد', 'success'); toast(data?.data?.message || 'پیام به علاقه‌مندی‌ها اضافه شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -74,7 +74,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const moveToTrashMutation = useMutation({ const moveToTrashMutation = useMutation({
mutationFn: moveMessageToTrash, mutationFn: ({ messageId, mailbox }: { messageId: number; mailbox: string }) => moveMessageToTrash(messageId, mailbox),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیام به سطل زباله منتقل شد', 'success'); toast(data?.data?.message || 'پیام به سطل زباله منتقل شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -82,7 +82,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const deletePermanentlyMutation = useMutation({ const deletePermanentlyMutation = useMutation({
mutationFn: deleteMessagePermanently, mutationFn: ({ messageId, mailbox }: { messageId: number; mailbox: string }) => deleteMessagePermanently(messageId, mailbox),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیام به طور کامل حذف شد', 'success'); toast(data?.data?.message || 'پیام به طور کامل حذف شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -90,7 +90,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const sendEmailMutation = useMutation({ const sendEmailMutation = useMutation({
mutationFn: sendEmail, mutationFn: ({ messageId }: { messageId: number }) => sendEmail(messageId),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'ایمیل با موفقیت ارسال شد', 'success'); toast(data?.data?.message || 'ایمیل با موفقیت ارسال شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -107,7 +107,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const deleteDraftMutation = useMutation({ const deleteDraftMutation = useMutation({
mutationFn: deleteDraft, mutationFn: ({ messageId, mailbox }: { messageId: number; mailbox: string }) => deleteDraft(messageId, mailbox),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیش‌نویس حذف شد', 'success'); toast(data?.data?.message || 'پیش‌نویس حذف شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -115,7 +115,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const sendDraftMutation = useMutation({ const sendDraftMutation = useMutation({
mutationFn: sendDraft, mutationFn: ({ messageId }: { messageId: number }) => sendDraft(messageId),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیش‌نویس ارسال شد', 'success'); toast(data?.data?.message || 'پیش‌نویس ارسال شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -123,7 +123,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const unfavoriteMutation = useMutation({ const unfavoriteMutation = useMutation({
mutationFn: unfavoriteMessage, mutationFn: ({ messageId, mailbox }: { messageId: number; mailbox: string }) => unfavoriteMessage(messageId, mailbox),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیام از علاقه‌مندی‌ها حذف شد', 'success'); toast(data?.data?.message || 'پیام از علاقه‌مندی‌ها حذف شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -131,7 +131,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const unarchiveMutation = useMutation({ const unarchiveMutation = useMutation({
mutationFn: unarchiveMessage, mutationFn: ({ messageId, mailbox }: { messageId: number; mailbox: string }) => unarchiveMessage(messageId, mailbox),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیام از آرشیو خارج شد', 'success'); toast(data?.data?.message || 'پیام از آرشیو خارج شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -139,7 +139,7 @@ export const useEmailActions = (customRefetch?: () => void) => {
}); });
const restoreMutation = useMutation({ const restoreMutation = useMutation({
mutationFn: restoreMessage, mutationFn: ({ messageId, mailbox }: { messageId: number; mailbox: string }) => restoreMessage(messageId, mailbox),
onSuccess: (data) => { onSuccess: (data) => {
toast(data?.data?.message || 'پیام بازگردانی شد', 'success'); toast(data?.data?.message || 'پیام بازگردانی شد', 'success');
invalidateAllQueries(); invalidateAllQueries();
@@ -164,24 +164,24 @@ export const useEmailActions = (customRefetch?: () => void) => {
return { return {
// Bulk actions // Bulk actions
bulkAction: bulkActionMutation.mutate, bulkAction: bulkActionMutation.mutate,
bulkMarkAsSeen: (messageIds: number[]) => bulkMarkAsSeen: (messageIds: number[], mailbox: string) =>
bulkActionMutation.mutate({ messageIds, action: BulkActionType.SEEN }), bulkActionMutation.mutate({ messageIds, action: BulkActionType.SEEN, mailbox: mailbox }),
bulkArchive: (messageIds: number[]) => bulkArchive: (messageIds: number[], mailbox: string) =>
bulkActionMutation.mutate({ messageIds, action: BulkActionType.ARCHIVE }), bulkActionMutation.mutate({ messageIds, action: BulkActionType.ARCHIVE, mailbox: mailbox }),
bulkFavorite: (messageIds: number[]) => bulkFavorite: (messageIds: number[], mailbox: string) =>
bulkActionMutation.mutate({ messageIds, action: BulkActionType.FAVORITE }), bulkActionMutation.mutate({ messageIds, action: BulkActionType.FAVORITE, mailbox: mailbox }),
bulkMoveToTrash: (messageIds: number[]) => bulkMoveToTrash: (messageIds: number[], mailbox: string) =>
bulkActionMutation.mutate({ messageIds, action: BulkActionType.TRASH }), bulkActionMutation.mutate({ messageIds, action: BulkActionType.TRASH, mailbox: mailbox }),
bulkDelete: (messageIds: number[]) => bulkDelete: (messageIds: number[], mailbox: string) =>
bulkActionMutation.mutate({ messageIds, action: BulkActionType.DELETE }), bulkActionMutation.mutate({ messageIds, action: BulkActionType.DELETE, mailbox: mailbox }),
bulkJunk: (messageIds: number[]) => bulkJunk: (messageIds: number[], mailbox: string) =>
bulkActionMutation.mutate({ messageIds, action: BulkActionType.JUNK }), bulkActionMutation.mutate({ messageIds, action: BulkActionType.JUNK, mailbox: mailbox }),
bulkUnfavorite: (messageIds: number[]) => bulkUnfavorite: (messageIds: number[], mailbox: string) =>
bulkActionMutation.mutate({ messageIds, action: BulkActionType.UNFAVORITE }), bulkActionMutation.mutate({ messageIds, action: BulkActionType.UNFAVORITE, mailbox: mailbox }),
bulkUnarchive: (messageIds: number[]) => bulkUnarchive: (messageIds: number[], mailbox: string) =>
bulkActionMutation.mutate({ messageIds, action: BulkActionType.UNARCHIVE }), bulkActionMutation.mutate({ messageIds, action: BulkActionType.UNARCHIVE, mailbox: mailbox }),
bulkRestore: (messageIds: number[]) => bulkRestore: (messageIds: number[], mailbox: string) =>
bulkActionMutation.mutate({ messageIds, action: BulkActionType.RESTORE }), bulkActionMutation.mutate({ messageIds, action: BulkActionType.RESTORE, mailbox: mailbox }),
// Single actions (keeping for backward compatibility) // Single actions (keeping for backward compatibility)
markAsSeen: markAsSeenMutation.mutate, markAsSeen: markAsSeenMutation.mutate,
+1
View File
@@ -95,6 +95,7 @@
"NOTIFICATION": "اعلان", "NOTIFICATION": "اعلان",
"FINANCE": "مالی" "FINANCE": "مالی"
}, },
"success": "عملیات با موفقیت انجام شد",
"attachment_select": "اضافه کردن فایل ضمیمه", "attachment_select": "اضافه کردن فایل ضمیمه",
"domain": { "domain": {
"table": { "table": {
+4 -4
View File
@@ -82,13 +82,13 @@ const List: FC = () => {
const handleDeleteSelected = () => { const handleDeleteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkDelete(messageIds); emailActions.bulkDelete(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleRestoreSelected = () => { const handleRestoreSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkRestore(messageIds); emailActions.bulkRestore(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
@@ -96,12 +96,12 @@ const List: FC = () => {
{ {
label: 'بازگردانی', label: 'بازگردانی',
icon: <RecoveryConvert size={16} color="black" />, icon: <RecoveryConvert size={16} color="black" />,
onClick: () => emailActions.restore(message.id), onClick: () => emailActions.restore({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'حذف دائم', label: 'حذف دائم',
icon: <Trash size={16} color="red" />, icon: <Trash size={16} color="red" />,
onClick: () => emailActions.deletePermanently(message.id), onClick: () => emailActions.deletePermanently({ messageId: message.id, mailbox: message.mailbox }),
}, },
]; ];
+4 -4
View File
@@ -82,13 +82,13 @@ const List: FC = () => {
const handleDeleteSelected = () => { const handleDeleteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMoveToTrash(messageIds); emailActions.bulkMoveToTrash(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleUnarchiveSelected = () => { const handleUnarchiveSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkUnarchive(messageIds); emailActions.bulkUnarchive(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
@@ -96,12 +96,12 @@ const List: FC = () => {
{ {
label: 'خروج از آرشیو', label: 'خروج از آرشیو',
icon: <ArchiveSlash size={16} color="black" />, icon: <ArchiveSlash size={16} color="black" />,
onClick: () => emailActions.unarchive(message.id), onClick: () => emailActions.unarchive({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'حذف', label: 'حذف',
icon: <Trash size={16} color="red" />, icon: <Trash size={16} color="red" />,
onClick: () => emailActions.moveToTrash(message.id), onClick: () => emailActions.moveToTrash({ messageId: message.id, mailbox: message.mailbox }),
}, },
]; ];
+13 -5
View File
@@ -10,6 +10,7 @@ import { useGetDrafts } from './hooks/useDraftData';
import { DraftMessage } from './types/DraftTypes'; import { DraftMessage } from './types/DraftTypes';
import { formatDate } from '@/config/func'; import { formatDate } from '@/config/func';
import { useEmailActions } from '@/hooks/useEmailActions'; import { useEmailActions } from '@/hooks/useEmailActions';
import { useSharedStore } from '@/shared/store/sharedStore';
const List: FC = () => { const List: FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@@ -17,6 +18,7 @@ const List: FC = () => {
const [currentPage, setCurrentPage] = useState(1); const [currentPage, setCurrentPage] = useState(1);
const [filters, setFilters] = useState<FilterValues>({}); const [filters, setFilters] = useState<FilterValues>({});
const emailActions = useEmailActions(); const emailActions = useEmailActions();
const { setDraftData, setOpenNewMessage, setEditingDraftId } = useSharedStore();
const { data: draftData, isLoading, refetch, isFetching } = useGetDrafts({ const { data: draftData, isLoading, refetch, isFetching } = useGetDrafts({
page: currentPage, page: currentPage,
@@ -113,14 +115,14 @@ const List: FC = () => {
const handleDeleteSelected = () => { const handleDeleteSelected = () => {
selectedMessages.forEach(message => { selectedMessages.forEach(message => {
emailActions.deleteDraft(message.id); emailActions.deleteDraft({ messageId: message.id, mailbox: message.mailbox });
}); });
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleSendSelected = () => { const handleSendSelected = () => {
selectedMessages.forEach(message => { selectedMessages.forEach(message => {
emailActions.sendDraft(message.id); emailActions.sendDraft({ messageId: message.id });
}); });
setSelectedMessages([]); setSelectedMessages([]);
}; };
@@ -134,17 +136,23 @@ const List: FC = () => {
}; };
const handleDeleteDraft = (messageId: number) => { const handleDeleteDraft = (messageId: number) => {
emailActions.deleteDraft(messageId); emailActions.deleteDraft({ messageId: messageId, mailbox: '' });
}; };
const handleSendDraft = (messageId: number) => { const handleSendDraft = (messageId: number) => {
emailActions.sendDraft(messageId); emailActions.sendDraft({ messageId: messageId });
}; };
const handleEditDraft = (messageId: number) => { const handleEditDraft = (messageId: number) => {
navigate(`/draft/edit/${messageId}`); navigate(`/draft/edit/${messageId}`);
}; };
const handleRowClick = (messageId: number, item: DraftMessage) => {
setDraftData(item);
setEditingDraftId(messageId);
setOpenNewMessage(true);
};
const getRowActions = (message: DraftMessage): RowActionItem[] => [ const getRowActions = (message: DraftMessage): RowActionItem[] => [
{ {
label: 'ارسال', label: 'ارسال',
@@ -193,7 +201,7 @@ const List: FC = () => {
selectedActions={selectedActions} selectedActions={selectedActions}
onSelectionChange={handleSelectionChange} onSelectionChange={handleSelectionChange}
selectedRows={selectedMessages} selectedRows={selectedMessages}
onRowClick={(id) => navigate(`/draft/edit/${id}`)} onRowClick={handleRowClick}
noDataMessage={ noDataMessage={
<div className="flex flex-col items-center "> <div className="flex flex-col items-center ">
<InfoCircle size={32} className="text-gray-300 mb-2" /> <InfoCircle size={32} className="text-gray-300 mb-2" />
+25
View File
@@ -43,3 +43,28 @@ export const useEditDraft = () => {
}, },
}); });
}; };
export const useUpdateDraft = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ messageId, draftData }: { messageId: string; draftData: DraftUpdateDto }) =>
api.updateDraft(messageId, draftData),
onSuccess: () => {
// Invalidate drafts queries to refresh the list
queryClient.invalidateQueries({ queryKey: ['drafts'] });
},
});
};
export const useSendDraft = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (messageId: string) => api.sendDraft(messageId),
onSuccess: () => {
// Invalidate drafts queries to refresh the list
queryClient.invalidateQueries({ queryKey: ['drafts'] });
},
});
};
+17 -1
View File
@@ -29,9 +29,25 @@ export const editDraft = async (
return data; return data;
}; };
export const updateDraft = async (
messageId: string,
draftData: DraftUpdateDto
): Promise<void> => {
const { data } = await axios.patch(
`/email/messages/drafts/${messageId}`,
draftData
);
return data;
};
export const sendDraft = async (messageId: string): Promise<void> => {
const { data } = await axios.post(`/email/messages/drafts/${messageId}/send`);
return data;
};
export const getDraftDetail = async ( export const getDraftDetail = async (
messageId: string messageId: string
): Promise<InboxMessage> => { ): Promise<InboxMessage> => {
const { data } = await axios.get(`/email/messages/drafts/${messageId}`); const { data } = await axios.get(`/email/messages/${messageId}`);
return data.data.message; return data.data.message;
}; };
+2
View File
@@ -104,4 +104,6 @@ export interface DraftUpdateDto {
text?: string; text?: string;
html?: string; html?: string;
attachments?: EmailAttachmentDto[]; attachments?: EmailAttachmentDto[];
isDraft?: boolean;
uploadOnly?: boolean;
} }
+6 -6
View File
@@ -83,19 +83,19 @@ const List: FC = () => {
const handleDeleteSelected = () => { const handleDeleteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMoveToTrash(messageIds); emailActions.bulkMoveToTrash(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleArchiveSelected = () => { const handleArchiveSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkArchive(messageIds); emailActions.bulkArchive(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleUnfavoriteSelected = () => { const handleUnfavoriteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkUnfavorite(messageIds); emailActions.bulkUnfavorite(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
@@ -103,17 +103,17 @@ const List: FC = () => {
{ {
label: 'حذف از علاقه‌مندی‌ها', label: 'حذف از علاقه‌مندی‌ها',
icon: <Star size={16} color="black" />, icon: <Star size={16} color="black" />,
onClick: () => emailActions.unfavorite(message.id), onClick: () => emailActions.unfavorite({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'بایگانی', label: 'بایگانی',
icon: <Archive size={16} color="black" />, icon: <Archive size={16} color="black" />,
onClick: () => emailActions.archive(message.id), onClick: () => emailActions.archive({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'حذف', label: 'حذف',
icon: <Trash size={16} color="red" />, icon: <Trash size={16} color="red" />,
onClick: () => emailActions.moveToTrash(message.id), onClick: () => emailActions.moveToTrash({ messageId: message.id, mailbox: message.mailbox }),
}, },
]; ];
+3 -3
View File
@@ -21,14 +21,14 @@ const DetailEmail: FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
const emailActions = useEmailActions() const emailActions = useEmailActions()
const { id } = useParams<{ id: string }>() const { id, mailbox } = useParams<{ id: string, mailbox: string }>()
const { email: userEmail } = useAuthStore() const { email: userEmail } = useAuthStore()
const [showAnswer, setShowAnswer] = useState<boolean>(false) const [showAnswer, setShowAnswer] = useState<boolean>(false)
const [value, setValue] = useState<string>('') const [value, setValue] = useState<string>('')
const [priority, setPriority] = useState<string>('') const [priority, setPriority] = useState<string>('')
// API hooks // API hooks
const { data: messageDetail, isLoading, error } = useGetMessageDetail(id || '', !!id) const { data: messageDetail, isLoading, error } = useGetMessageDetail(id || '', mailbox || '', !!id)
const sendEmailMutation = useSendEmail() const sendEmailMutation = useSendEmail()
// const saveDraftMutation = useSaveDraft() // const saveDraftMutation = useSaveDraft()
@@ -113,7 +113,7 @@ const DetailEmail: FC = () => {
useEffect(() => { useEffect(() => {
if (messageDetail && !messageDetail.seen && id) { if (messageDetail && !messageDetail.seen && id) {
console.log('Marking message as seen:', id, 'Current seen status:', messageDetail.seen) console.log('Marking message as seen:', id, 'Current seen status:', messageDetail.seen)
emailActions.markAsSeen(Number(id)) emailActions.markAsSeen({ messageId: Number(id), mailbox: mailbox || '' })
} }
}, [messageDetail, emailActions, id]) }, [messageDetail, emailActions, id])
+9 -9
View File
@@ -144,25 +144,25 @@ const List: FC = () => {
const handleDeleteSelected = () => { const handleDeleteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMoveToTrash(messageIds); emailActions.bulkMoveToTrash(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleArchiveSelected = () => { const handleArchiveSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkArchive(messageIds); emailActions.bulkArchive(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleMarkAsReadSelected = () => { const handleMarkAsReadSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMarkAsSeen(messageIds); emailActions.bulkMarkAsSeen(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleFavoriteSelected = () => { const handleFavoriteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkFavorite(messageIds); emailActions.bulkFavorite(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
@@ -170,22 +170,22 @@ const List: FC = () => {
{ {
label: message.seen ? 'علامت‌گذاری به عنوان خوانده نشده' : 'علامت‌گذاری به عنوان خوانده شده', label: message.seen ? 'علامت‌گذاری به عنوان خوانده نشده' : 'علامت‌گذاری به عنوان خوانده شده',
icon: <Edit size={16} color="black" />, icon: <Edit size={16} color="black" />,
onClick: () => emailActions.markAsSeen(message.id), onClick: () => emailActions.markAsSeen({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'اضافه به علاقه‌مندی‌ها', label: 'اضافه به علاقه‌مندی‌ها',
icon: <Star1 size={16} color="black" />, icon: <Star1 size={16} color="black" />,
onClick: () => emailActions.favorite(message.id), onClick: () => emailActions.favorite({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'بایگانی', label: 'بایگانی',
icon: <Archive size={16} color="black" />, icon: <Archive size={16} color="black" />,
onClick: () => emailActions.archive(message.id), onClick: () => emailActions.archive({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'حذف', label: 'حذف',
icon: <Trash size={16} color="red" />, icon: <Trash size={16} color="red" />,
onClick: () => emailActions.moveToTrash(message.id), onClick: () => emailActions.moveToTrash({ messageId: message.id, mailbox: message.mailbox }),
}, },
]; ];
@@ -229,7 +229,7 @@ const List: FC = () => {
selectedActions={selectedActions} selectedActions={selectedActions}
onSelectionChange={handleSelectionChange} onSelectionChange={handleSelectionChange}
selectedRows={selectedMessages} selectedRows={selectedMessages}
onRowClick={(id) => navigate(`/mail/${id}`)} onRowClick={(id, item) => navigate(`/mail/${id}/${item.mailbox}`)}
noDataMessage={ noDataMessage={
<div className="flex flex-col items-center"> <div className="flex flex-col items-center">
<InfoCircle size={32} className="text-gray-300 mb-2" /> <InfoCircle size={32} className="text-gray-300 mb-2" />
+3 -3
View File
@@ -31,10 +31,10 @@ export const useGetInbox = (query: MessageListQueryDto) => {
}); });
}; };
export const useGetMessageDetail = (messageId: string, enabled: boolean = true) => { export const useGetMessageDetail = (messageId: string, mailbox: string, enabled: boolean = true) => {
return useQuery({ return useQuery({
queryKey: ['message', messageId], queryKey: ['message', messageId, mailbox],
queryFn: () => api.getMessageDetail(messageId), queryFn: () => api.getMessageDetail(messageId, mailbox),
enabled: !!messageId && enabled, enabled: !!messageId && enabled,
staleTime: 0, // Always fetch fresh data for message details staleTime: 0, // Always fetch fresh data for message details
}); });
+3 -2
View File
@@ -32,10 +32,11 @@ export const getInbox = async (
}; };
export const getMessageDetail = async ( export const getMessageDetail = async (
messageId: string messageId: string,
mailbox: string
): Promise<MessageDetail> => { ): Promise<MessageDetail> => {
const { data }: { data: MessageDetailResponse } = await axios.get( const { data }: { data: MessageDetailResponse } = await axios.get(
`/email/messages/${messageId}` `/email/messages/${messageId}?mailbox=${mailbox}`
); );
return data.data.message; return data.data.message;
}; };
+1
View File
@@ -303,4 +303,5 @@ export enum BulkActionType {
export interface BulkActionRequest { export interface BulkActionRequest {
messageIds: number[]; messageIds: number[];
action: BulkActionType; action: BulkActionType;
mailbox: string;
} }
+6 -6
View File
@@ -90,19 +90,19 @@ const List: FC = () => {
const handleDeleteSelected = () => { const handleDeleteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMoveToTrash(messageIds); emailActions.bulkMoveToTrash(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleArchiveSelected = () => { const handleArchiveSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkArchive(messageIds); emailActions.bulkArchive(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleFavoriteSelected = () => { const handleFavoriteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkFavorite(messageIds); emailActions.bulkFavorite(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
@@ -110,17 +110,17 @@ const List: FC = () => {
{ {
label: 'اضافه به علاقه‌مندی‌ها', label: 'اضافه به علاقه‌مندی‌ها',
icon: <Star size={16} color="black" />, icon: <Star size={16} color="black" />,
onClick: () => emailActions.favorite(message.id), onClick: () => emailActions.favorite({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'بایگانی', label: 'بایگانی',
icon: <Archive size={16} color="black" />, icon: <Archive size={16} color="black" />,
onClick: () => emailActions.archive(message.id), onClick: () => emailActions.archive({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'حذف', label: 'حذف',
icon: <Trash size={16} color="red" />, icon: <Trash size={16} color="red" />,
onClick: () => emailActions.moveToTrash(message.id), onClick: () => emailActions.moveToTrash({ messageId: message.id, mailbox: message.mailbox }),
}, },
]; ];
+8 -8
View File
@@ -94,8 +94,8 @@ const List: FC = () => {
setCurrentPage(1); setCurrentPage(1);
}; };
const handleRowClick = (id: number) => { const handleRowClick = (id: number, item: InboxMessage) => {
navigate(`${Paths.detailEmail}${id}`); navigate(`${Paths.detailEmail}${id}/${item.mailbox}`);
}; };
const handleSelectionChange = (selectedRows: InboxMessage[]) => { const handleSelectionChange = (selectedRows: InboxMessage[]) => {
@@ -104,19 +104,19 @@ const List: FC = () => {
const handleDeleteSelected = () => { const handleDeleteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMoveToTrash(messageIds); emailActions.bulkMoveToTrash(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleArchiveSelected = () => { const handleArchiveSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkArchive(messageIds); emailActions.bulkArchive(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
const handleFavoriteSelected = () => { const handleFavoriteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkFavorite(messageIds); emailActions.bulkFavorite(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
@@ -124,17 +124,17 @@ const List: FC = () => {
{ {
label: 'اضافه به علاقه‌مندی‌ها', label: 'اضافه به علاقه‌مندی‌ها',
icon: <Star size={16} color="black" />, icon: <Star size={16} color="black" />,
onClick: () => emailActions.favorite(message.id), onClick: () => emailActions.favorite({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'بایگانی', label: 'بایگانی',
icon: <Archive size={16} color="black" />, icon: <Archive size={16} color="black" />,
onClick: () => emailActions.archive(message.id), onClick: () => emailActions.archive({ messageId: message.id, mailbox: message.mailbox }),
}, },
{ {
label: 'حذف', label: 'حذف',
icon: <Trash size={16} color="red" />, icon: <Trash size={16} color="red" />,
onClick: () => emailActions.moveToTrash(message.id), onClick: () => emailActions.moveToTrash({ messageId: message.id, mailbox: message.mailbox }),
}, },
]; ];
+2 -2
View File
@@ -81,7 +81,7 @@ const List: FC = () => {
const handleDeleteSelected = () => { const handleDeleteSelected = () => {
const messageIds = selectedMessages.map(message => message.id); const messageIds = selectedMessages.map(message => message.id);
emailActions.bulkMoveToTrash(messageIds); emailActions.bulkMoveToTrash(messageIds, selectedMessages[0].mailbox);
setSelectedMessages([]); setSelectedMessages([]);
}; };
@@ -100,7 +100,7 @@ const List: FC = () => {
{ {
label: 'حذف', label: 'حذف',
icon: <Trash size={16} color="red" />, icon: <Trash size={16} color="red" />,
onClick: () => emailActions.moveToTrash(message.id), onClick: () => emailActions.moveToTrash({ messageId: message.id, mailbox: message.mailbox }),
}, },
]; ];
+1 -1
View File
@@ -25,7 +25,7 @@ const AppRouter: FC = () => {
<Route path={Paths.favorite} element={<FavoriteList />} /> <Route path={Paths.favorite} element={<FavoriteList />} />
<Route path={Paths.spam} element={<SpamList />} /> <Route path={Paths.spam} element={<SpamList />} />
<Route path={Paths.search} element={<SearchList />} /> <Route path={Paths.search} element={<SearchList />} />
<Route path={Paths.detailEmail + ':id'} element={<DetailEmail />} /> <Route path={Paths.detailEmail + ':id/:mailbox'} element={<DetailEmail />} />
<Route path={Paths.login} element={<Login />} /> <Route path={Paths.login} element={<Login />} />
<Route path={Paths.setting} element={<Setting />} /> <Route path={Paths.setting} element={<Setting />} />
</Routes> </Routes>
+54 -21
View File
@@ -3,43 +3,68 @@ import { BulkActionRequest } from "../pages/received/types/Types";
// Bulk action for multiple messages // Bulk action for multiple messages
export const bulkAction = async (request: BulkActionRequest) => { export const bulkAction = async (request: BulkActionRequest) => {
const { data } = await axios.post(`/email/messages/bulk-action`, request); const { data } = await axios.post(
`/email/messages/bulk-action?mailbox=${request.mailbox}`,
request
);
return data; return data;
}; };
// Mark message as seen // Mark message as seen
export const markMessageAsSeen = async (messageId: number) => { export const markMessageAsSeen = async (messageId: number, mailbox: string) => {
const { data } = await axios.patch(`/email/messages/${messageId}/seen`); const { data } = await axios.patch(
`/email/messages/${messageId}/seen?mailbox=${mailbox}`
);
return data; return data;
}; };
// Move message to archive // Move message to archive
export const moveMessageToArchive = async (messageId: number) => { export const moveMessageToArchive = async (
const { data } = await axios.patch(`/email/messages/${messageId}/archive`); messageId: number,
mailbox: string
) => {
const { data } = await axios.patch(
`/email/messages/${messageId}/archive?mailbox=${mailbox}`
);
return data; return data;
}; };
// Move message to favorite // Move message to favorite
export const moveMessageToFavorite = async (messageId: number) => { export const moveMessageToFavorite = async (
const { data } = await axios.patch(`/email/messages/${messageId}/favorite`); messageId: number,
mailbox: string
) => {
const { data } = await axios.patch(
`/email/messages/${messageId}/favorite?mailbox=${mailbox}`
);
return data; return data;
}; };
// Move message to trash // Move message to trash
export const moveMessageToTrash = async (messageId: number) => { export const moveMessageToTrash = async (
const { data } = await axios.patch(`/email/messages/${messageId}/trash`); messageId: number,
mailbox: string
) => {
const { data } = await axios.patch(
`/email/messages/${messageId}/trash?mailbox=${mailbox}`
);
return data; return data;
}; };
// Delete message permanently (only for trash mailbox) // Delete message permanently (only for trash mailbox)
export const deleteMessagePermanently = async (messageId: number) => { export const deleteMessagePermanently = async (
const { data } = await axios.delete(`/email/messages/${messageId}`); messageId: number,
mailbox: string
) => {
const { data } = await axios.delete(
`/email/messages/${messageId}?mailbox=${mailbox}`
);
return data; return data;
}; };
// Send email // Send email
export const sendEmail = async (emailData: unknown) => { export const sendEmail = async (messageId: number) => {
const { data } = await axios.post(`/email/send`, emailData); const { data } = await axios.post(`/email/send?messageId=${messageId}`);
return data; return data;
}; };
@@ -53,8 +78,10 @@ export const updateDraft = async (messageId: number, draftData: unknown) => {
}; };
// Delete draft // Delete draft
export const deleteDraft = async (messageId: number) => { export const deleteDraft = async (messageId: number, mailbox: string) => {
const { data } = await axios.delete(`/email/messages/drafts/${messageId}`); const { data } = await axios.delete(
`/email/messages/drafts/${messageId}?mailbox=${mailbox}`
);
return data; return data;
}; };
@@ -65,19 +92,25 @@ export const sendDraft = async (messageId: number) => {
}; };
// Unfavorite message // Unfavorite message
export const unfavoriteMessage = async (messageId: number) => { export const unfavoriteMessage = async (messageId: number, mailbox: string) => {
const { data } = await axios.patch(`/email/messages/${messageId}/unfavorite`); const { data } = await axios.patch(
`/email/messages/${messageId}/unfavorite?mailbox=${mailbox}`
);
return data; return data;
}; };
// Unarchive message // Unarchive message
export const unarchiveMessage = async (messageId: number) => { export const unarchiveMessage = async (messageId: number, mailbox: string) => {
const { data } = await axios.patch(`/email/messages/${messageId}/unarchive`); const { data } = await axios.patch(
`/email/messages/${messageId}/unarchive?mailbox=${mailbox}`
);
return data; return data;
}; };
// Restore message from trash // Restore message from trash
export const restoreMessage = async (messageId: number) => { export const restoreMessage = async (messageId: number, mailbox: string) => {
const { data } = await axios.patch(`/email/messages/${messageId}/restore`); const { data } = await axios.patch(
`/email/messages/${messageId}/restore?mailbox=${mailbox}`
);
return data; return data;
}; };
+4
View File
@@ -6,4 +6,8 @@ export const useSharedStore = create<SharedStoreType>((set) => ({
setOpenSidebar: (value) => set({ openSidebar: value }), setOpenSidebar: (value) => set({ openSidebar: value }),
openNewMessage: false, openNewMessage: false,
setOpenNewMessage: (value) => set({ openNewMessage: value }), setOpenNewMessage: (value) => set({ openNewMessage: value }),
draftData: null,
setDraftData: (draft) => set({ draftData: draft }),
editingDraftId: null,
setEditingDraftId: (id) => set({ editingDraftId: id }),
})); }));
+6
View File
@@ -1,6 +1,12 @@
import { DraftMessage } from "@/pages/draft/types/DraftTypes";
export type SharedStoreType = { export type SharedStoreType = {
openSidebar: boolean; openSidebar: boolean;
setOpenSidebar: (value: boolean) => void; setOpenSidebar: (value: boolean) => void;
openNewMessage: boolean; openNewMessage: boolean;
setOpenNewMessage: (value: boolean) => void; setOpenNewMessage: (value: boolean) => void;
draftData: DraftMessage | null;
setDraftData: (draft: DraftMessage | null) => void;
editingDraftId: number | null;
setEditingDraftId: (id: number | null) => void;
}; };