forward and reply

This commit is contained in:
hamid zarghami
2025-07-22 12:37:35 +03:30
parent b31b64a7f9
commit 6bb82bcbac
12 changed files with 681 additions and 196 deletions
+2 -2
View File
@@ -6,8 +6,8 @@ VITE_DANAK_BASE_URL ='https://api.danakcorp.com'
VITE_SOCKET_URL = 'wss://dmail-api.danakcorp.com/email'
# VITE_SOCKET_URL = 'ws://192.168.1.118:4000/email'
VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
# VITE_BASE_URL = 'http://192.168.1.118:4000'
# VITE_BASE_URL = 'https://dmail-api.danakcorp.com'
VITE_BASE_URL = 'http://192.168.1.118:4000'
VITE_SERVICE_ID = 'e51afdc3-ea0b-49cf-8f49-2a6f131b024e'
@@ -0,0 +1,45 @@
import { FC } from 'react'
import { EmojiHappy } from 'iconsax-react'
import Reply from './Reply'
import Forward from './Forward'
import { MessageDetail } from '@/pages/received/types/Types'
interface EmailActionsProps {
messageDetail: MessageDetail
className?: string
showReactions?: boolean
}
const EmailActions: FC<EmailActionsProps> = ({
messageDetail,
className = '',
showReactions = true
}) => {
return (
<div className={`flex mt-9 justify-end ${className}`}>
<div className='flex gap-5 text-sm items-center'>
{showReactions && (
<EmojiHappy
size={24}
color='black'
className='cursor-pointer hover:opacity-70 transition-opacity'
/>
)}
<Reply
originalMessage={messageDetail}
className='border-r border-border pr-5'
onClose={() => { }}
/>
<Forward
originalMessage={messageDetail}
className='border-r border-border pr-5'
onClose={() => { }}
/>
</div>
</div>
)
}
export default EmailActions
+1 -11
View File
@@ -12,20 +12,10 @@ const EmailEditor: FC<EmailEditorProps> = ({
onChange,
placeholder = ''
}) => {
const modules = {
toolbar: [
[{ header: '1' }, { header: '2' }, { font: [] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['bold', 'italic', 'underline'],
['link', 'image'],
[{ align: [] }],
['clean']
]
}
return (
<ReactQuill
modules={modules}
theme="snow"
value={value}
onChange={onChange}
+43 -23
View File
@@ -4,21 +4,23 @@ import Button from '@/components/Button'
import Select from '@/components/Select'
interface EmailFormActionsProps {
priority: string
onPriorityChange: (priority: string) => void
onSend: () => void
onSaveDraft: () => void
isSending: boolean
isSavingDraft: boolean
onClose?: () => void
onSaveDraft?: () => void
isSavingDraft?: boolean
priority?: string
onPriorityChange?: (priority: string) => void
}
const EmailFormActions: FC<EmailFormActionsProps> = ({
priority,
onPriorityChange,
onSend,
onSaveDraft,
isSending,
isSavingDraft
onClose,
onSaveDraft,
isSavingDraft = false,
priority,
onPriorityChange
}) => {
const { t } = useTranslation('global')
@@ -30,22 +32,40 @@ const EmailFormActions: FC<EmailFormActionsProps> = ({
return (
<div className='flex flex-col gap-4 sm:flex-row pt-4'>
<div>
<Select
items={priorityOptions}
placeholder={t('new_message.select_priority')}
className='xl:w-[190px] min-w-[130px]'
value={priority}
onChange={(e) => onPriorityChange(e.target.value)}
/>
</div>
{/* Priority Selector */}
{priority !== undefined && onPriorityChange && (
<div>
<Select
items={priorityOptions}
placeholder={t('new_message.select_priority')}
className='xl:w-[190px] min-w-[130px]'
value={priority}
onChange={(e) => onPriorityChange(e.target.value)}
/>
</div>
)}
<div className='flex-1 justify-end flex gap-3'>
<Button
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
label={t('new_message.draft')}
onClick={onSaveDraft}
loading={isSavingDraft}
/>
{/* Save Draft Button */}
{onSaveDraft && (
<Button
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
label={t('new_message.draft')}
onClick={onSaveDraft}
loading={isSavingDraft}
/>
)}
{/* Close Button */}
{onClose && !onSaveDraft && (
<Button
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
label={t('common.close')}
onClick={onClose}
/>
)}
{/* Send Button */}
<Button
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
label={t('new_message.send')}
+240
View File
@@ -0,0 +1,240 @@
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ArrowDown2, CloseSquare } from 'iconsax-react'
import AvatarImage from '@/assets/images/avatar.svg'
import ForwardIcon from '@/assets/images/forward.svg'
import EmailEditor from './EmailEditor'
import EmailFormActions from './EmailFormActions'
import EmailInput from './EmailInput'
import { MessageDetail } from '@/pages/received/types/Types'
import { useAuthStore } from '@/pages/auth/store/AuthStore'
import { useSendEmail } from '@/pages/received/hooks/useEmailData'
import { SendEmailDto } from '@/pages/received/types/Types'
import { toast } from '@/components/Toast'
interface ForwardProps {
originalMessage: MessageDetail
onClose: () => void
onExpand?: () => void
className?: string
showInitialButton?: boolean
}
const Forward: FC<ForwardProps> = ({
originalMessage,
onClose,
onExpand,
className = '',
showInitialButton = true
}) => {
const { t } = useTranslation()
const { email: userEmail } = useAuthStore()
const sendEmailMutation = useSendEmail()
const [isExpanded, setIsExpanded] = useState<boolean>(false)
const [toEmails, setToEmails] = useState<string[]>([])
const [content, setContent] = useState<string>('')
const expandForward = () => {
setIsExpanded(true)
// Pre-fill content with original message
const forwardContent = `
<br/><br/>
<div dir="rtl">
---------- پیام ارسالی ----------<br/>
<strong>از:</strong> ${originalMessage.from.name || originalMessage.from.address} &lt;${originalMessage.from.address}&gt;<br/>
<strong>تاریخ:</strong> ${originalMessage.date}<br/>
<strong>موضوع:</strong> ${originalMessage.subject}<br/>
<strong>به:</strong> ${originalMessage.to.map(recipient => recipient.address).join(', ')}<br/>
<br/>
${originalMessage.html ? originalMessage.html.join('') : originalMessage.text}
</div>
`
setContent(forwardContent)
onExpand?.()
}
const handleClose = async () => {
// اگر محتوا یا ایمیل وارد شده، پیش‌نویس ذخیره کن
if (content.trim() || toEmails.length > 0) {
// TODO: Implement save draft functionality
toast('پیش‌نویس ذخیره شد', 'success')
}
// بسته کردن و برگشت به حالت قبل
collapseForward()
onClose() // این خط مهمه تا parent component بفهمه
}
const collapseForward = () => {
setIsExpanded(false)
resetForm()
onClose()
}
const resetForm = () => {
setToEmails([])
setContent('')
setIsExpanded(false)
}
const handleSendForward = async () => {
if (toEmails.length === 0) {
toast('لطفا حداقل یک گیرنده وارد کنید', 'error')
return false
}
if (!content.trim()) {
toast('لطفا متن پیام را وارد کنید', 'error')
return false
}
// Format date to Persian
const originalDate = new Date(originalMessage.date)
const persianDate = new Intl.DateTimeFormat('fa-IR', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
}).format(originalDate)
const htmlContent =
content +
"<br /><br />" +
"<div dir='rtl'>---------- پیام ارسالی ----------<br/>" +
`<strong>از:</strong> ${originalMessage.from.name || originalMessage.from.address} &lt;${originalMessage.from.address}&gt;<br/>` +
`<strong>تاریخ:</strong> ${persianDate}<br/>` +
`<strong>موضوع:</strong> ${originalMessage.subject}<br/>` +
`<strong>به:</strong> ${originalMessage.to.map(recipient => recipient.address).join(', ')}<br/>` +
"<br/>" +
(originalMessage?.html?.[0] || originalMessage?.text || '') + "</div>"
const forwardData: SendEmailDto = {
from: { address: userEmail || 'sender@example.com' },
to: toEmails.map(email => ({ address: email })),
subject: originalMessage.subject.startsWith('Fwd:')
? originalMessage.subject
: `Fwd: ${originalMessage.subject}`,
html: htmlContent,
text: content.replace(/<[^>]*>/g, ''),
isForward: true,
}
try {
await sendEmailMutation.mutateAsync(forwardData)
toast('پیام با موفقیت ارسال شد', 'success')
resetForm()
onClose?.()
return true
} catch {
const errorMessage = 'خطا در ارسال پیام'
toast(errorMessage, 'error')
return false
}
}
// اگر showInitialButton true باشه و هنوز expand نشده، فقط دکمه نشون بده
if (showInitialButton && !isExpanded) {
return (
<div className={`flex gap-5 text-sm ${className}`}>
<div
onClick={expandForward}
className='flex gap-2 cursor-pointer items-center border-r border-border pr-5 hover:text-primary transition-colors'
>
<img src={ForwardIcon} className='w-[17px]' />
<div>{t('mail.forward')}</div>
</div>
</div>
)
}
// در غیر این صورت editor رو نشون بده
// (یا showInitialButton false هست یا isExpanded true هست)
return (
<div className={`mt-6 w-full ${className}`}>
{/* Forward Header */}
<div className='flex items-center justify-between mb-4 p-3 bg-gray-50 rounded-lg border'>
<div className='flex items-center gap-2 text-sm text-gray-600'>
<img src={ForwardIcon} className='w-4 h-4' />
<span>ارسال به دیگران</span>
</div>
<div className='flex items-center gap-2'>
<button
onClick={collapseForward}
className='p-1 hover:bg-gray-200 rounded transition-colors'
title='بستن'
>
<ArrowDown2 size={16} />
</button>
<button
onClick={() => {
collapseForward()
onClose?.()
}}
className='p-1 hover:bg-gray-200 rounded transition-colors'
title='لغو'
>
<CloseSquare size={16} />
</button>
</div>
</div>
{/* Forward Content */}
<div className='flex gap-4'>
<img src={AvatarImage} className='size-10 flex-shrink-0' />
<div className='w-full'>
{/* To Field */}
<div className='mb-4'>
<EmailInput
toEmails={toEmails}
onAddEmail={(email) => {
if (!toEmails.includes(email)) {
setToEmails([...toEmails, email])
}
}}
onRemoveEmail={(email) => {
setToEmails(toEmails.filter(e => e !== email))
}}
suggestions={[]}
/>
</div>
{/* Subject Field */}
<div className='mb-4 text-sm'>
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded border'>
<span className='text-gray-600 min-w-fit'>موضوع:</span>
<span className='text-gray-800'>
{originalMessage.subject.startsWith('Fwd:')
? originalMessage.subject
: `Fwd: ${originalMessage.subject}`}
</span>
</div>
</div>
{/* Editor */}
<div className='mb-4'>
<EmailEditor
value={content}
onChange={setContent}
placeholder='پیام خود را اضافه کنید...'
/>
</div>
{/* Actions */}
<EmailFormActions
onSend={handleSendForward}
isSending={sendEmailMutation.isPending}
onClose={handleClose}
/>
</div>
</div>
</div>
)
}
export default Forward
+1 -1
View File
@@ -95,7 +95,7 @@ const NewMessage: FC = () => {
}`}
onClick={handleClose}
/>
<div className={`fixed left-2 right-2 bottom-2 md:left-4 md:right-4 md:bottom-4 bg-white rounded-2xl md:rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] p-4 md:p-6 xl:p-8 w-auto xl:w-[800px] max-h-[90vh] overflow-y-auto transition-transform duration-300 ease-out ${isClosing ? 'translate-y-[120%]' : isOpening ? 'translate-y-full' : 'translate-y-0'
<div className={`fixed left-2 bottom-2 md:left-4 md:bottom-4 bg-white rounded-2xl md:rounded-3xl shadow-[0_0_20px_rgba(0,0,0,0.1)] z-[9999] p-4 md:p-6 xl:p-8 w-auto xl:w-[800px] max-h-[90vh] overflow-y-auto transition-transform duration-300 ease-out ${isClosing ? 'translate-y-[120%]' : isOpening ? 'translate-y-full' : 'translate-y-0'
}`}>
{/* Header */}
<div className='flex justify-between items-center mb-6 md:mb-8'>
+162
View File
@@ -0,0 +1,162 @@
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import { ArrowDown2, CloseSquare } from 'iconsax-react'
import AvatarImage from '@/assets/images/avatar.svg'
import AnswerIcon from '@/assets/images/answer.svg'
import EmailEditor from './EmailEditor'
import EmailFormActions from './EmailFormActions'
import { useReply } from './hooks/useReply'
import { MessageDetail } from '@/pages/received/types/Types'
import { toast } from '@/components/Toast'
interface ReplyProps {
originalMessage: MessageDetail
onClose?: () => void
onExpand?: () => void
className?: string
showInitialButton?: boolean
}
const Reply: FC<ReplyProps> = ({
originalMessage,
onClose,
onExpand,
className = '',
showInitialButton = true
}) => {
const { t } = useTranslation()
const {
content,
isExpanded,
isSending,
setContent,
expandReply,
collapseReply,
handleSendReply,
handleSaveDraft
} = useReply({
originalMessage,
onSuccess: () => {
onClose?.()
}
})
const handleExpand = () => {
expandReply()
onExpand?.()
}
const handleClose = async () => {
// اگر محتوایی وارد شده، پیش‌نویس ذخیره کن
if (content.trim()) {
await handleSaveDraft()
toast('پیش‌نویس ذخیره شد', 'success')
}
// بسته کردن و برگشت به حالت قبل
collapseReply()
onClose?.() // این خط مهمه تا parent component بفهمه
}
// اگر showInitialButton true باشه و هنوز expand نشده، فقط دکمه نشون بده
if (showInitialButton && !isExpanded) {
return (
<div className={`flex justify-end ${className}`}>
<div className='flex gap-5 text-sm'>
<div
onClick={handleExpand}
className='flex gap-2 cursor-pointer items-center hover:text-primary transition-colors'
>
<img src={AnswerIcon} className='w-[17px]' />
<div>{t('mail.answer')}</div>
</div>
</div>
</div>
)
}
const handleSend = () => {
handleSendReply(originalMessage?.html[0])
collapseReply()
}
// در غیر این صورت editor رو نشون بده
// (یا showInitialButton false هست یا isExpanded true هست)
return (
<div className={`mt-6 w-full ${className}`}>
{/* Reply Header */}
<div className='flex items-center justify-between mb-4 p-3 bg-gray-50 rounded-lg border'>
<div className='flex items-center gap-2 text-sm text-gray-600'>
<img src={AnswerIcon} className='w-4 h-4' />
<span>پاسخ به {originalMessage.from.name || originalMessage.from.address}</span>
</div>
<div className='flex items-center gap-2'>
<button
onClick={collapseReply}
className='p-1 hover:bg-gray-200 rounded transition-colors'
title='بستن'
>
<ArrowDown2 size={16} />
</button>
<button
onClick={() => {
collapseReply()
onClose?.()
}}
className='p-1 hover:bg-gray-200 rounded transition-colors'
title='لغو'
>
<CloseSquare size={16} />
</button>
</div>
</div>
{/* Reply Content */}
<div className='flex gap-4'>
<img src={AvatarImage} className='size-10 flex-shrink-0' />
<div className='w-full'>
{/* To Field */}
<div className='mb-4 text-sm'>
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded border'>
<span className='text-gray-600 min-w-fit'>به:</span>
<span className='text-gray-800'>{originalMessage.from.address}</span>
</div>
</div>
{/* Subject Field */}
<div className='mb-4 text-sm'>
<div className='flex items-center gap-2 p-2 bg-gray-50 rounded border'>
<span className='text-gray-600 min-w-fit'>موضوع:</span>
<span className='text-gray-800'>
{originalMessage.subject.startsWith('Re:')
? originalMessage.subject
: `Re: ${originalMessage.subject}`}
</span>
</div>
</div>
{/* Editor */}
<div className='mb-4'>
<EmailEditor
value={content}
onChange={setContent}
placeholder='پیام خود را بنویسید...'
/>
</div>
{/* Actions */}
<EmailFormActions
onSend={handleSend}
onClose={handleClose}
isSending={isSending}
/>
</div>
</div>
</div>
)
}
export default Reply
+137
View File
@@ -0,0 +1,137 @@
import { useState } from "react";
import { useAuthStore } from "@/pages/auth/store/AuthStore";
import { useSendEmail } from "@/pages/received/hooks/useEmailData";
import { SendEmailDto } from "@/pages/received/types/Types";
import { MessageDetail } from "@/pages/received/types/Types";
import { toast } from "@/components/Toast";
interface UseReplyProps {
originalMessage: MessageDetail;
onSuccess?: () => void;
onError?: (error: string) => void;
}
export const useReply = ({
originalMessage,
onSuccess,
onError,
}: UseReplyProps) => {
const { email: userEmail } = useAuthStore();
const [content, setContent] = useState<string>("");
const [isExpanded, setIsExpanded] = useState<boolean>(false);
// API hooks
const sendEmailMutation = useSendEmail();
const resetForm = () => {
setContent("");
setIsExpanded(false);
};
const handleSendReply = async (html: string) => {
if (!content.trim()) {
toast("لطفا متن پیام را وارد کنید", "error");
return false;
}
if (!originalMessage) {
toast("اطلاعات پیام یافت نشد", "error");
return false;
}
// Format date to Persian
const originalDate = new Date(originalMessage.date);
const persianDate = new Intl.DateTimeFormat("fa-IR", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
}).format(originalDate);
const htmlContent =
content +
"<br /><br />" +
`<div dir="rtl">در تاریخ ${persianDate} ${originalMessage.from.name} <${originalMessage.from.address}> نوشت:</div>` +
"<br /><br />" +
`<div dir="rtl">${html}</div>`;
const replyData: SendEmailDto = {
from: { address: userEmail || "sender@example.com" },
to: [
{
address: originalMessage.from.address,
name: originalMessage.from.name,
},
],
replyTo: {
name: originalMessage.from.name,
address: originalMessage.from.address,
},
subject: originalMessage.subject.startsWith("Re:")
? originalMessage.subject
: `Re: ${originalMessage.subject}`,
html: htmlContent,
text: content.replace(/<[^>]*>/g, ""),
};
try {
await sendEmailMutation.mutateAsync(replyData);
toast("پاسخ با موفقیت ارسال شد", "success");
resetForm();
onSuccess?.();
return true;
} catch {
const errorMessage = "خطا در ارسال پاسخ";
toast(errorMessage, "error");
onError?.(errorMessage);
return false;
}
};
const handleSaveDraft = async () => {
if (!content.trim()) {
toast("متن پیام خالی است", "error");
return false;
}
try {
// TODO: Implement save draft functionality
toast("پیش‌نویس ذخیره شد", "success");
return true;
} catch {
const errorMessage = "خطا در ذخیره پیش‌نویس";
toast(errorMessage, "error");
onError?.(errorMessage);
return false;
}
};
const expandReply = () => {
setIsExpanded(true);
};
const collapseReply = () => {
setIsExpanded(false);
resetForm();
};
return {
// State
content,
isExpanded,
isSending: sendEmailMutation.isPending,
// Handlers
setContent,
expandReply,
collapseReply,
handleSendReply,
handleSaveDraft,
resetForm,
// Original message data
originalMessage,
};
};
+3
View File
@@ -348,3 +348,6 @@ textarea::placeholder {
backdrop-filter: blur(44px);
}
strong {
font-weight: bold;
}
+5 -1
View File
@@ -23,6 +23,9 @@
"required": "این فیلد اجباری است",
"invalid_email": "فرمت ایمیل نامعتبر است"
},
"common": {
"close": "بستن"
},
"received": {
"title": "دریافتی ها",
"from_date": "از تاریخ",
@@ -140,7 +143,8 @@
"FAVORITE": "نشان‌شده‌ها",
"Junk": "هرزنامه‌ها",
"SENT": "ارسال شده‌ها",
"TRASH": "سطل زباله"
"TRASH": "سطل زباله",
"Sent Mail": "ارسال شده ها"
},
"auth": {
"welcome": "خوش آمدید",
+41 -158
View File
@@ -4,112 +4,26 @@ import { useTranslation } from 'react-i18next'
import { useParams } from 'react-router-dom'
import AvatarImage from '@/assets/images/avatar.svg'
import { DocumentDownload, EmojiHappy } from 'iconsax-react'
import ForwardIcon from '@/assets/images/forward.svg'
import AnswerIcon from '@/assets/images/answer.svg'
import Header from './Components/Header'
import ReactQuill from 'react-quill-new';
import Select from '@/components/Select'
import { useGetMessageDetail, useSendEmail, useDownloadAttachment } from './hooks/useEmailData'
import { useAuthStore } from '../auth/store/AuthStore'
import { SendEmailDto } from './types/Types'
import { useGetMessageDetail, useDownloadAttachment } from './hooks/useEmailData'
import { toast } from '@/components/Toast'
import { formatDate, sanitizeEmailHTML, detectTextDirection } from '@/config/func'
import SkeletonDetail from './Components/SkeletonDetail'
import { useEmailActions } from '@/hooks/useEmailActions'
import { MailboxEnum } from './enum/Enum'
import Reply from '@/components/newMessage/Reply'
import Forward from '@/components/newMessage/Forward'
const DetailEmail: FC = () => {
const { t } = useTranslation()
const emailActions = useEmailActions()
const { id, mailbox } = useParams<{ id: string, mailbox: string }>()
const { email: userEmail } = useAuthStore()
const [showAnswer, setShowAnswer] = useState<boolean>(false)
const [value, setValue] = useState<string>('')
const [priority, setPriority] = useState<string>('')
const [activeAction, setActiveAction] = useState<'reply' | 'forward' | null>(null)
// API hooks
const { data: messageDetail, isLoading, error } = useGetMessageDetail(id || '', mailbox || '')
const sendEmailMutation = useSendEmail()
const downloadAttachmentMutation = useDownloadAttachment()
// const saveDraftMutation = useSaveDraft()
const priorityOptions = [
{ value: 'high', label: 'بالا' },
{ value: 'medium', label: 'متوسط' },
{ value: 'low', label: 'پایین' },
]
const handleSendReply = async () => {
if (!value.trim()) {
toast('لطفا متن پیام را وارد کنید', 'error')
return
}
if (!messageDetail) {
toast('اطلاعات پیام یافت نشد', 'error')
return
}
const replyData: SendEmailDto = {
from: { address: userEmail || 'sender@example.com' },
to: [{ address: messageDetail.from.address, name: messageDetail.from.name }],
subject: `Re: ${messageDetail.subject}`,
html: value,
text: value.replace(/<[^>]*>/g, ''),
reference: {
inReplyTo: messageDetail.messageId,
originalMessageId: messageDetail.id.toString()
}
}
try {
await sendEmailMutation.mutateAsync(replyData)
toast('پاسخ با موفقیت ارسال شد', 'success')
setShowAnswer(false)
setValue('')
setPriority('')
} catch {
toast('خطا در ارسال پاسخ', 'error')
}
}
const handleSaveDraft = async () => {
if (!value.trim()) {
toast('متن پیام خالی است', 'error')
return
}
if (!messageDetail) {
toast('اطلاعات پیام یافت نشد', 'error')
return
}
// const draftData: SendEmailDto = {
// from: { address: userEmail || 'sender@example.com' },
// to: [{ address: messageDetail.from.address, name: messageDetail.from.name }],
// subject: `Re: ${messageDetail.subject}`,
// html: value,
// text: value.replace(/<[^>]*>/g, ''),
// isDraft: true,
// reference: {
// inReplyTo: messageDetail.messageId,
// originalMessageId: messageDetail.id.toString()
// }
// }
try {
// await saveDraftMutation.mutateAsync(draftData)
toast('پیش‌نویس ذخیره شد', 'success')
} catch {
toast('خطا در ذخیره پیش‌نویس', 'error')
}
}
const handleForward = () => {
// TODO: Implement forward functionality
toast('قابلیت ارسال به دیگران به زودی اضافه خواهد شد', 'info')
}
const handleDownloadAttachment = async (attachmentId: string, filename: string) => {
if (!id) {
@@ -245,78 +159,47 @@ const DetailEmail: FC = () => {
)}
</div>
{
!showAnswer ?
<div className='flex mt-9 justify-end'>
<div className='flex gap-5 text-sm'>
<EmojiHappy size={24} color='black' />
{/* Email Actions */}
{activeAction === null && (
<div className='flex mt-9 justify-end'>
<div className='flex gap-5 text-sm'>
<EmojiHappy size={24} color='black' />
<div onClick={() => setShowAnswer(true)} className='flex gap-2 cursor-pointer items-center border-r border-border pr-5'>
<img src={AnswerIcon} className='w-[17px]' />
<div>{t('mail.answer')}</div>
</div>
<Reply
originalMessage={messageDetail}
className='border-r border-border pr-5'
showInitialButton={true}
onClose={() => setActiveAction(null)}
onExpand={() => setActiveAction('reply')}
/>
<div onClick={handleForward} className='flex gap-2 cursor-pointer items-center border-r border-border pr-5'>
<img src={ForwardIcon} className='w-[17px]' />
<div>{t('mail.forward')}</div>
</div>
</div>
<Forward
originalMessage={messageDetail}
showInitialButton={true}
onClose={() => setActiveAction(null)}
onExpand={() => setActiveAction('forward')}
/>
</div>
:
<div className='mt-9 flex gap-4'>
<img src={AvatarImage} className='size-10' />
<div className='w-full'>
<div className='mb-2 text-sm'>
{t('mail.message')}
</div>
<ReactQuill
modules={{
toolbar: [
[{ header: '1' }, { header: '2' }, { font: [] }],
[{ list: 'ordered' }, { list: 'bullet' }],
['bold', 'italic', 'underline'],
['link', 'image'],
[{ align: [] }],
['clean']
]
}}
theme="snow"
value={value}
onChange={setValue}
style={{ minHeight: '120px', width: '100%' }}
className='text-sm'
/>
</div>
)}
<div>
<div className='flex flex-col gap-4 sm:flex-row pt-4'>
<div>
<Select
items={priorityOptions}
placeholder={t('new_message.select_priority')}
className='xl:w-[190px]'
value={priority}
onChange={(e) => setPriority(e.target.value)}
/>
</div>
<div className='flex-1 justify-end flex gap-3'>
<Button
className='!w-full sm:!w-fit px-6 md:px-10 bg-white text-black border border-primary order-2 sm:order-1'
label={t('new_message.draft')}
onClick={handleSaveDraft}
// loading={saveDraftMutation.isPending}
/>
<Button
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
label={t('new_message.send')}
onClick={handleSendReply}
loading={sendEmailMutation.isPending}
/>
</div>
</div>
</div>
</div>
</div>
}
{/* Full width Reply */}
{activeAction === 'reply' && (
<Reply
originalMessage={messageDetail}
showInitialButton={false}
onClose={() => setActiveAction(null)}
/>
)}
{/* Full width Forward */}
{activeAction === 'forward' && (
<Forward
originalMessage={messageDetail}
showInitialButton={false}
onClose={() => setActiveAction(null)}
/>
)}
</div>
)
}
+1
View File
@@ -50,6 +50,7 @@ export interface SendEmailDto {
sendTime?: string;
uploadOnly?: boolean;
envelope?: EmailEnvelopeDto;
isForward?: boolean;
}
export interface MessageListQueryDto {