recive email + save draft and list draft + skeleton email detail
This commit is contained in:
@@ -85,7 +85,7 @@ const Table = <T extends RowDataType>({
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className={`bg-white ${roundedClass} py-6 md:py-8 text-center text-gray-500`}>
|
||||
<div className={`bg-white ${roundedClass} text-center text-gray-500`}>
|
||||
{noDataMessage}
|
||||
</div>
|
||||
);
|
||||
@@ -155,7 +155,7 @@ const Table = <T extends RowDataType>({
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} className="px-3 md:px-6 py-6 md:py-8 text-center text-gray-500">
|
||||
<td colSpan={columns.length + (selectable ? 1 : 0) + (rowActions ? 1 : 0)} className="px-3 md:px-6 pb-6 md:pb-8 text-center text-gray-500">
|
||||
{noDataMessage}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { FC, useState, useEffect } from 'react'
|
||||
import { FC, useState, useEffect, useRef } from 'react'
|
||||
import { CloseCircle } from 'iconsax-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSharedStore } from '@/shared/store/sharedStore'
|
||||
import { useAuthStore } from '../../pages/auth/store/AuthStore'
|
||||
import { useSendEmail, useSaveDraft } from '../../pages/received/hooks/useEmailData'
|
||||
import { useSendEmail } from '../../pages/received/hooks/useEmailData'
|
||||
import { SendEmailDto } from '../../pages/received/types/Types'
|
||||
import Input from '../Input'
|
||||
import ReactQuill from 'react-quill-new';
|
||||
@@ -11,6 +11,7 @@ import Button from '../Button'
|
||||
import Select from '../Select'
|
||||
import UploadButton from '../UploadButton'
|
||||
import { toast } from '../Toast'
|
||||
import { ErrorType } from '@/helpers/types'
|
||||
|
||||
|
||||
const NewMessage: FC = () => {
|
||||
@@ -28,10 +29,15 @@ const NewMessage: FC = () => {
|
||||
const [subject, setSubject] = useState('')
|
||||
const [priority, setPriority] = useState('')
|
||||
|
||||
// Loading states
|
||||
const [isSending, setIsSending] = useState(false)
|
||||
const [isSavingDraft, setIsSavingDraft] = useState(false)
|
||||
|
||||
// Auto-add email functionality
|
||||
const typingTimeoutRef = useRef<number | null>(null)
|
||||
|
||||
// API hooks
|
||||
const sendEmailMutation = useSendEmail()
|
||||
const saveDraftMutation = useSaveDraft()
|
||||
|
||||
useEffect(() => {
|
||||
if (openNewMessage) {
|
||||
@@ -70,63 +76,92 @@ const NewMessage: FC = () => {
|
||||
return emailRegex.test(email)
|
||||
}
|
||||
|
||||
const addEmailToList = (email: string) => {
|
||||
if (!isValidEmail(email)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (toEmails.includes(email)) {
|
||||
toast('این ایمیل قبلاً اضافه شده است', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
setToEmails([...toEmails, email])
|
||||
setTo('')
|
||||
}
|
||||
|
||||
const handleToChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.value
|
||||
setTo(newValue)
|
||||
|
||||
// Clear existing timeout
|
||||
if (typingTimeoutRef.current) {
|
||||
clearTimeout(typingTimeoutRef.current)
|
||||
}
|
||||
|
||||
// Set new timeout to auto-add email after user stops typing
|
||||
if (newValue.trim()) {
|
||||
typingTimeoutRef.current = window.setTimeout(() => {
|
||||
if (isValidEmail(newValue.trim())) {
|
||||
addEmailToList(newValue.trim())
|
||||
}
|
||||
}, 1500) // Wait 1.5 seconds after user stops typing
|
||||
}
|
||||
}
|
||||
|
||||
const handleToBlur = () => {
|
||||
// Clear timeout when input loses focus
|
||||
if (typingTimeoutRef.current) {
|
||||
clearTimeout(typingTimeoutRef.current)
|
||||
}
|
||||
|
||||
// Auto-add email if valid when input loses focus
|
||||
if (to.trim() && isValidEmail(to.trim())) {
|
||||
addEmailToList(to.trim())
|
||||
}
|
||||
}
|
||||
|
||||
const handleToKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter' && to.trim()) {
|
||||
e.preventDefault()
|
||||
const email = to.trim()
|
||||
|
||||
if (!isValidEmail(email)) {
|
||||
toast('لطفا یک ایمیل معتبر وارد کنید', 'error')
|
||||
return
|
||||
// Clear timeout when Enter is pressed
|
||||
if (typingTimeoutRef.current) {
|
||||
clearTimeout(typingTimeoutRef.current)
|
||||
}
|
||||
|
||||
if (toEmails.includes(email)) {
|
||||
toast('این ایمیل قبلاً اضافه شده است', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
setToEmails([...toEmails, email])
|
||||
setTo('')
|
||||
addEmailToList(to.trim())
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up timeout on component unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (typingTimeoutRef.current) {
|
||||
clearTimeout(typingTimeoutRef.current)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
|
||||
const removeEmail = (emailToRemove: string) => {
|
||||
setToEmails(toEmails.filter(email => email !== emailToRemove))
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
setTo('')
|
||||
setToEmails([])
|
||||
setSubject('')
|
||||
setValue('')
|
||||
setPriority('')
|
||||
}
|
||||
|
||||
|
||||
const handleSend = async () => {
|
||||
if (toEmails.length === 0 || !subject) {
|
||||
const handleEmailAction = async (isDraft: boolean = false) => {
|
||||
if (!isDraft && (toEmails.length === 0 || !subject)) {
|
||||
toast('لطفا تمام فیلدهای مورد نیاز را پر کنید', 'error')
|
||||
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
|
||||
}
|
||||
|
||||
try {
|
||||
await sendEmailMutation.mutateAsync(emailData)
|
||||
toast('ایمیل با موفقیت ارسال شد', 'success')
|
||||
handleClose()
|
||||
// Reset form
|
||||
setTo('')
|
||||
setToEmails([])
|
||||
setSubject('')
|
||||
setValue('')
|
||||
setPriority('')
|
||||
} catch {
|
||||
toast('خطا در ارسال ایمیل', 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveDraft = async () => {
|
||||
if (toEmails.length === 0 && !subject && !value) {
|
||||
if (isDraft && toEmails.length === 0 && !subject && !value) {
|
||||
toast('حداقل یکی از فیلدها باید پر باشد', 'error')
|
||||
return
|
||||
}
|
||||
@@ -136,25 +171,37 @@ const NewMessage: FC = () => {
|
||||
to: parseEmailAddresses(toEmails),
|
||||
subject,
|
||||
html: value,
|
||||
text: value.replace(/<[^>]*>/g, ''),
|
||||
isDraft: true,
|
||||
text: value.replace(/<[^>]*>/g, ''), // Strip HTML for text version
|
||||
...(isDraft && { isDraft: true, uploadOnly: true }),
|
||||
}
|
||||
|
||||
try {
|
||||
await saveDraftMutation.mutateAsync(emailData)
|
||||
toast('پیشنویس ذخیره شد', 'success')
|
||||
handleClose()
|
||||
// Reset form
|
||||
setTo('')
|
||||
setToEmails([])
|
||||
setSubject('')
|
||||
setValue('')
|
||||
setPriority('')
|
||||
} catch {
|
||||
toast('خطا در ذخیره پیشنویس', 'error')
|
||||
// Set appropriate loading state
|
||||
if (isDraft) {
|
||||
setIsSavingDraft(true)
|
||||
} else {
|
||||
setIsSending(true)
|
||||
}
|
||||
|
||||
await sendEmailMutation.mutateAsync(emailData, {
|
||||
onSuccess: (data) => {
|
||||
toast(data.message, 'success')
|
||||
handleClose()
|
||||
resetForm()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast(error.response?.data?.error.message[0], 'error')
|
||||
},
|
||||
onSettled: () => {
|
||||
// Reset loading states
|
||||
setIsSending(false)
|
||||
setIsSavingDraft(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleSend = () => handleEmailAction(false)
|
||||
const handleSaveDraft = () => handleEmailAction(true)
|
||||
|
||||
const priorityOptions = [
|
||||
{ value: 'high', label: 'بالا' },
|
||||
{ value: 'medium', label: 'متوسط' },
|
||||
@@ -170,7 +217,7 @@ const NewMessage: FC = () => {
|
||||
}`}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
<div className={`fixed left-2 right-2 bottom-2 md:left-4 md:right-4 md:bottom-4 xl:left-8 xl:right-auto xl: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 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'
|
||||
}`}>
|
||||
{/* Header */}
|
||||
<div className='flex justify-between items-center mb-6 md:mb-8'>
|
||||
@@ -209,7 +256,8 @@ const NewMessage: FC = () => {
|
||||
{/* Input Field */}
|
||||
<input
|
||||
value={to}
|
||||
onChange={(e) => setTo(e.target.value)}
|
||||
onChange={handleToChange}
|
||||
onBlur={handleToBlur}
|
||||
onKeyDown={handleToKeyDown}
|
||||
placeholder={toEmails.length === 0 ? "ایمیل را وارد کرده و Enter بزنید" : ""}
|
||||
className="flex-1 min-w-32 bg-transparent outline-none"
|
||||
@@ -263,13 +311,13 @@ const NewMessage: FC = () => {
|
||||
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}
|
||||
loading={isSavingDraft}
|
||||
/>
|
||||
<Button
|
||||
className='w-full sm:w-fit px-6 md:px-10 order-1 sm:order-2'
|
||||
label={t('new_message.send')}
|
||||
onClick={handleSend}
|
||||
loading={sendEmailMutation.isPending}
|
||||
loading={isSending}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user