remove comments
This commit is contained in:
+1
-9
@@ -40,7 +40,6 @@ i18next.init({
|
|||||||
const queryClient = new QueryClient({
|
const queryClient = new QueryClient({
|
||||||
queryCache: new QueryCache({
|
queryCache: new QueryCache({
|
||||||
onError: async (error: IApiErrorRepsonse) => {
|
onError: async (error: IApiErrorRepsonse) => {
|
||||||
// نمایش پیغام خطا از API
|
|
||||||
const errorMessage = error?.response?.error?.message?.[0] || undefined;
|
const errorMessage = error?.response?.error?.message?.[0] || undefined;
|
||||||
|
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
@@ -49,9 +48,8 @@ const queryClient = new QueryClient({
|
|||||||
|
|
||||||
if (error?.response?.status === 401) {
|
if (error?.response?.status === 401) {
|
||||||
try {
|
try {
|
||||||
// Use a flag to track if refresh is in progress
|
|
||||||
if (window.isRefreshingToken) {
|
if (window.isRefreshingToken) {
|
||||||
// Wait for the refresh to complete
|
|
||||||
await new Promise(resolve => {
|
await new Promise(resolve => {
|
||||||
const checkComplete = setInterval(() => {
|
const checkComplete = setInterval(() => {
|
||||||
if (!window.isRefreshingToken) {
|
if (!window.isRefreshingToken) {
|
||||||
@@ -63,7 +61,6 @@ const queryClient = new QueryClient({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the flag to indicate refresh is in progress
|
|
||||||
window.isRefreshingToken = true;
|
window.isRefreshingToken = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -71,15 +68,12 @@ const queryClient = new QueryClient({
|
|||||||
const { data } = await refreshToken({ refreshToken: refreshTokenValue || '' });
|
const { data } = await refreshToken({ refreshToken: refreshTokenValue || '' });
|
||||||
|
|
||||||
if (data?.accessToken?.token) {
|
if (data?.accessToken?.token) {
|
||||||
// Save the new token
|
|
||||||
await setToken(data?.accessToken?.token);
|
await setToken(data?.accessToken?.token);
|
||||||
|
|
||||||
// If refresh token also returned, update it
|
|
||||||
if (data.refreshToken?.token) {
|
if (data.refreshToken?.token) {
|
||||||
await setRefreshToken(data.refreshToken?.token);
|
await setRefreshToken(data.refreshToken?.token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry the failed request
|
|
||||||
queryClient.invalidateQueries();
|
queryClient.invalidateQueries();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
@@ -90,7 +84,6 @@ const queryClient = new QueryClient({
|
|||||||
}
|
}
|
||||||
} catch (refreshError: unknown) {
|
} catch (refreshError: unknown) {
|
||||||
console.error('Token refresh failed:', refreshError);
|
console.error('Token refresh failed:', refreshError);
|
||||||
// Clear tokens and redirect to login
|
|
||||||
await removeToken();
|
await removeToken();
|
||||||
await removeRefreshToken();
|
await removeRefreshToken();
|
||||||
window.location.href = '/auth/login';
|
window.location.href = '/auth/login';
|
||||||
@@ -102,7 +95,6 @@ const queryClient = new QueryClient({
|
|||||||
queries: {
|
queries: {
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
retry: false
|
retry: false
|
||||||
// staleTime: 86400000 // 1 day in milliseconds
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,11 +23,9 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
|||||||
onError: () => null,
|
onError: () => null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sound settings state
|
|
||||||
const [soundEnabled, setSoundEnabled] = useState(true);
|
const [soundEnabled, setSoundEnabled] = useState(true);
|
||||||
const [soundVolume, setSoundVolume] = useState(0.7);
|
const [soundVolume, setSoundVolume] = useState(0.7);
|
||||||
|
|
||||||
// Load sound settings from localStorage
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedEnabled = localStorage.getItem('notificationSoundEnabled');
|
const savedEnabled = localStorage.getItem('notificationSoundEnabled');
|
||||||
const savedVolume = localStorage.getItem('notificationSoundVolume');
|
const savedVolume = localStorage.getItem('notificationSoundVolume');
|
||||||
@@ -40,26 +38,21 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Notification sound hook
|
|
||||||
const { playSound } = useNotificationSound({
|
const { playSound } = useNotificationSound({
|
||||||
enabled: soundEnabled,
|
enabled: soundEnabled,
|
||||||
volume: soundVolume,
|
volume: soundVolume,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleNewEmail = useCallback((email: EmailPayload) => {
|
const handleNewEmail = useCallback((email: EmailPayload) => {
|
||||||
|
setNotifications(prev => [email, ...prev.slice(0, 9)]);
|
||||||
|
|
||||||
// Add to notifications list
|
|
||||||
setNotifications(prev => [email, ...prev.slice(0, 9)]); // Keep only 10 latest
|
|
||||||
|
|
||||||
// Show browser notification if permission granted
|
|
||||||
if (Notification.permission === 'granted') {
|
if (Notification.permission === 'granted') {
|
||||||
new Notification(`📧 ایمیل جدید: ${email.subject}`, {
|
new Notification(`📧 ایمیل جدید: ${email.subject}`, {
|
||||||
body: `از: ${email.from.name || email.from.address}\n${email.preview}`,
|
body: `از: ${email.from.name || email.from.address}\n${email.preview}`,
|
||||||
tag: `email-${email.messageId}`, // Prevent duplicates
|
tag: `email-${email.messageId}`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play notification sound
|
|
||||||
playSound();
|
playSound();
|
||||||
}, [playSound]);
|
}, [playSound]);
|
||||||
|
|
||||||
@@ -68,14 +61,12 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleEmailRead = useCallback((data: { messageId: number }) => {
|
const handleEmailRead = useCallback((data: { messageId: number }) => {
|
||||||
// Remove from notifications if it was there
|
|
||||||
setNotifications(prev =>
|
setNotifications(prev =>
|
||||||
prev.filter(notification => notification.messageId !== data.messageId)
|
prev.filter(notification => notification.messageId !== data.messageId)
|
||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleEmailSent = useCallback((data: EmailStatusPayload) => {
|
const handleEmailSent = useCallback((data: EmailStatusPayload) => {
|
||||||
// Remove from notifications if it was there
|
|
||||||
setNotifications(prev =>
|
setNotifications(prev =>
|
||||||
prev.filter(notification => notification.messageId !== data.messageId)
|
prev.filter(notification => notification.messageId !== data.messageId)
|
||||||
);
|
);
|
||||||
@@ -88,7 +79,6 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
|||||||
onEmailSent: handleEmailSent,
|
onEmailSent: handleEmailSent,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Request notification permission on mount
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (Notification.permission === 'default') {
|
if (Notification.permission === 'default') {
|
||||||
Notification.requestPermission();
|
Notification.requestPermission();
|
||||||
@@ -97,11 +87,9 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{/* Connection Status */}
|
|
||||||
<div className={`absolute -top-2 -right-2 w-3 h-3 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'
|
<div className={`absolute -top-2 -right-2 w-3 h-3 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'
|
||||||
}`}></div>
|
}`}></div>
|
||||||
|
|
||||||
{/* Test Sound Button */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
playSound();
|
playSound();
|
||||||
@@ -112,7 +100,6 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
|||||||
<VolumeHigh size={20} color="black" />
|
<VolumeHigh size={20} color="black" />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Notification Bell */}
|
|
||||||
<div
|
<div
|
||||||
className="relative cursor-pointer p-2 rounded-full hover:bg-gray-100 transition-colors"
|
className="relative cursor-pointer p-2 rounded-full hover:bg-gray-100 transition-colors"
|
||||||
onClick={() => setIsVisible(!isVisible)}
|
onClick={() => setIsVisible(!isVisible)}
|
||||||
@@ -125,7 +112,6 @@ export const EmailNotifications: React.FC<EmailNotificationsProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Notifications Dropdown */}
|
|
||||||
{isVisible && (
|
{isVisible && (
|
||||||
<div className="absolute top-full left-0 w-80 max-h-96 bg-white border border-gray-200 rounded-lg shadow-lg z-50 overflow-hidden">
|
<div className="absolute top-full left-0 w-80 max-h-96 bg-white border border-gray-200 rounded-lg shadow-lg z-50 overflow-hidden">
|
||||||
<div className="p-3 bg-gray-50 border-b border-gray-200 flex justify-between items-center">
|
<div className="p-3 bg-gray-50 border-b border-gray-200 flex justify-between items-center">
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ interface EmojiReactionProps {
|
|||||||
const EmojiReaction: FC<EmojiReactionProps> = ({ onEmojiSelect, className = '' }) => {
|
const EmojiReaction: FC<EmojiReactionProps> = ({ onEmojiSelect, className = '' }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
|
||||||
// استفاده از hook موجود برای تشخیص کلیک خارج از المان
|
|
||||||
const containerRef = useOutsideClick(() => {
|
const containerRef = useOutsideClick(() => {
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
setIsOpen(false)
|
setIsOpen(false)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import DefaulModal from './DefaulModal';
|
|||||||
import moment from 'moment-jalaali'
|
import moment from 'moment-jalaali'
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
|
|
||||||
// تعریف نوع داده برای فیلدهای مختلف
|
|
||||||
export type DateFieldType = {
|
export type DateFieldType = {
|
||||||
type: 'date';
|
type: 'date';
|
||||||
name: string;
|
name: string;
|
||||||
@@ -32,7 +31,6 @@ export type InputFieldType = {
|
|||||||
|
|
||||||
export type FieldType = DateFieldType | SelectFieldType | InputFieldType;
|
export type FieldType = DateFieldType | SelectFieldType | InputFieldType;
|
||||||
|
|
||||||
// تعریف نوع داده برای مقادیر فیلترها
|
|
||||||
export type FilterValues = Record<string, string | null>;
|
export type FilterValues = Record<string, string | null>;
|
||||||
|
|
||||||
interface FiltersProps {
|
interface FiltersProps {
|
||||||
@@ -41,7 +39,7 @@ interface FiltersProps {
|
|||||||
initialValues?: FilterValues;
|
initialValues?: FilterValues;
|
||||||
className?: string;
|
className?: string;
|
||||||
fieldClassName?: string;
|
fieldClassName?: string;
|
||||||
searchField?: string; // نام فیلد جستجو که باید در انتها نمایش داده شود
|
searchField?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Filters: FC<FiltersProps> = ({
|
const Filters: FC<FiltersProps> = ({
|
||||||
@@ -50,23 +48,20 @@ const Filters: FC<FiltersProps> = ({
|
|||||||
initialValues = {},
|
initialValues = {},
|
||||||
className = "mt-6 md:mt-8 xl:mt-10 flex flex-col md:flex-row justify-between items-start md:items-center gap-4",
|
className = "mt-6 md:mt-8 xl:mt-10 flex flex-col md:flex-row justify-between items-start md:items-center gap-4",
|
||||||
fieldClassName = "flex flex-col sm:flex-row gap-3 md:gap-4 w-full md:w-auto",
|
fieldClassName = "flex flex-col sm:flex-row gap-3 md:gap-4 w-full md:w-auto",
|
||||||
searchField = "search" // پیشفرض فیلد سرچ با نام search
|
searchField = "search"
|
||||||
}) => {
|
}) => {
|
||||||
const [filters, setFilters] = useState<FilterValues>({});
|
const [filters, setFilters] = useState<FilterValues>({});
|
||||||
const [isFiltersOpen, setIsFiltersOpen] = useState(false);
|
const [isFiltersOpen, setIsFiltersOpen] = useState(false);
|
||||||
const [inputValues, setInputValues] = useState<Record<string, string>>({});
|
const [inputValues, setInputValues] = useState<Record<string, string>>({});
|
||||||
const onChangeRef = useRef(onChange);
|
const onChangeRef = useRef(onChange);
|
||||||
|
|
||||||
// آپدیت کردن ref وقتی onChange تغییر میکند
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onChangeRef.current = onChange;
|
onChangeRef.current = onChange;
|
||||||
}, [onChange]);
|
}, [onChange]);
|
||||||
|
|
||||||
// تنظیم مقادیر اولیه
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Object.keys(initialValues).length > 0) {
|
if (Object.keys(initialValues).length > 0) {
|
||||||
setFilters(initialValues);
|
setFilters(initialValues);
|
||||||
// تنظیم مقادیر input برای فیلدهای نوع input
|
|
||||||
const inputFields: Record<string, string> = {};
|
const inputFields: Record<string, string> = {};
|
||||||
fields.forEach(field => {
|
fields.forEach(field => {
|
||||||
if (field.type === 'input' && initialValues[field.name]) {
|
if (field.type === 'input' && initialValues[field.name]) {
|
||||||
@@ -75,7 +70,6 @@ const Filters: FC<FiltersProps> = ({
|
|||||||
});
|
});
|
||||||
setInputValues(inputFields);
|
setInputValues(inputFields);
|
||||||
} else {
|
} else {
|
||||||
// تنظیم مقادیر پیشفرض از فیلدها
|
|
||||||
const defaultValues: FilterValues = {};
|
const defaultValues: FilterValues = {};
|
||||||
const defaultInputs: Record<string, string> = {};
|
const defaultInputs: Record<string, string> = {};
|
||||||
fields.forEach(field => {
|
fields.forEach(field => {
|
||||||
@@ -95,7 +89,6 @@ const Filters: FC<FiltersProps> = ({
|
|||||||
}
|
}
|
||||||
}, [fields, initialValues, onChange]);
|
}, [fields, initialValues, onChange]);
|
||||||
|
|
||||||
// Debounce برای فیلدهای input
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timeouts: Record<string, number> = {};
|
const timeouts: Record<string, number> = {};
|
||||||
|
|
||||||
@@ -170,13 +163,11 @@ const Filters: FC<FiltersProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// جداسازی فیلد جستجو از سایر فیلدها
|
|
||||||
const searchFieldObj = fields.find(field => field.name === searchField);
|
const searchFieldObj = fields.find(field => field.name === searchField);
|
||||||
const otherFields = fields.filter(field => field.name !== searchField);
|
const otherFields = fields.filter(field => field.name !== searchField);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{/* آیکون فیلتر برای موبایل */}
|
|
||||||
<div className="flex md:hidden items-center justify-between w-full mb-2">
|
<div className="flex md:hidden items-center justify-between w-full mb-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsFiltersOpen(!isFiltersOpen)}
|
onClick={() => setIsFiltersOpen(!isFiltersOpen)}
|
||||||
@@ -192,7 +183,6 @@ const Filters: FC<FiltersProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* فیلترها برای دسکتاپ (همیشه نمایش داده میشوند) */}
|
|
||||||
<div className="hidden md:flex md:flex-row justify-between items-center gap-4 w-full">
|
<div className="hidden md:flex md:flex-row justify-between items-center gap-4 w-full">
|
||||||
<div className={fieldClassName}>
|
<div className={fieldClassName}>
|
||||||
{otherFields.map(renderField)}
|
{otherFields.map(renderField)}
|
||||||
@@ -204,7 +194,6 @@ const Filters: FC<FiltersProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* فیلترها برای موبایل (در قالب مودال) */}
|
|
||||||
<DefaulModal
|
<DefaulModal
|
||||||
open={isFiltersOpen}
|
open={isFiltersOpen}
|
||||||
close={() => setIsFiltersOpen(false)}
|
close={() => setIsFiltersOpen(false)}
|
||||||
|
|||||||
@@ -42,13 +42,11 @@ const Input: FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (props.seprator) {
|
if (props.seprator) {
|
||||||
const inputValue = event.target.value.replace(/,/g, ''); // حذف کاماها
|
const inputValue = event.target.value.replace(/,/g, '');
|
||||||
const formatted = formatNumber(inputValue);
|
const formatted = formatNumber(inputValue);
|
||||||
|
|
||||||
// بهروزرسانی مقدار قالببندیشده
|
|
||||||
setFormattedValue(formatted);
|
setFormattedValue(formatted);
|
||||||
|
|
||||||
// ارسال مقدار خام به `onChange` والد
|
|
||||||
props.onChange?.({
|
props.onChange?.({
|
||||||
...event,
|
...event,
|
||||||
target: {
|
target: {
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ const Pagination: React.FC<PaginationProps> = ({
|
|||||||
onNext,
|
onNext,
|
||||||
onPrevious,
|
onPrevious,
|
||||||
}) => {
|
}) => {
|
||||||
// اگر hasNext فالس باشد، pagination نمایش داده نمیشود
|
|
||||||
if (!hasNext && !previousCursor) return null;
|
if (!hasNext && !previousCursor) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -40,22 +40,19 @@ const RowActionsDropdown: React.FC<RowActionsDropdownProps> = ({ actions, classN
|
|||||||
const rect = buttonRef.current.getBoundingClientRect();
|
const rect = buttonRef.current.getBoundingClientRect();
|
||||||
const isMobile = window.innerWidth < 768;
|
const isMobile = window.innerWidth < 768;
|
||||||
const dropdownWidth = isMobile ? 140 : 150;
|
const dropdownWidth = isMobile ? 140 : 150;
|
||||||
const dropdownHeight = actions.length * (isMobile ? 36 : 40); // تخمین ارتفاع منو
|
const dropdownHeight = actions.length * (isMobile ? 36 : 40);
|
||||||
|
|
||||||
let left = rect.left + window.scrollX - dropdownWidth + rect.width;
|
let left = rect.left + window.scrollX - dropdownWidth + rect.width;
|
||||||
let top = rect.bottom + window.scrollY;
|
let top = rect.bottom + window.scrollY;
|
||||||
|
|
||||||
// بررسی اینکه منو از سمت راست خارج نشود
|
|
||||||
if (left + dropdownWidth > window.innerWidth) {
|
if (left + dropdownWidth > window.innerWidth) {
|
||||||
left = rect.left + window.scrollX - dropdownWidth;
|
left = rect.left + window.scrollX - dropdownWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// بررسی اینکه منو از سمت چپ خارج نشود
|
|
||||||
if (left < 0) {
|
if (left < 0) {
|
||||||
left = isMobile ? 20 : 40;
|
left = isMobile ? 20 : 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
// بررسی اینکه منو از پایین خارج نشود
|
|
||||||
if (top + dropdownHeight > window.innerHeight + window.scrollY) {
|
if (top + dropdownHeight > window.innerHeight + window.scrollY) {
|
||||||
top = rect.top + window.scrollY - dropdownHeight;
|
top = rect.top + window.scrollY - dropdownHeight;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
|||||||
volume: volume,
|
volume: volume,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load settings from localStorage on mount
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedEnabled = localStorage.getItem('notificationSoundEnabled');
|
const savedEnabled = localStorage.getItem('notificationSoundEnabled');
|
||||||
const savedVolume = localStorage.getItem('notificationSoundVolume');
|
const savedVolume = localStorage.getItem('notificationSoundVolume');
|
||||||
@@ -28,7 +27,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Save settings to localStorage when changed
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
localStorage.setItem('notificationSoundEnabled', isEnabled.toString());
|
localStorage.setItem('notificationSoundEnabled', isEnabled.toString());
|
||||||
localStorage.setItem('notificationSoundVolume', volume.toString());
|
localStorage.setItem('notificationSoundVolume', volume.toString());
|
||||||
@@ -37,7 +35,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
|||||||
const handleVolumeChange = (newVolume: number) => {
|
const handleVolumeChange = (newVolume: number) => {
|
||||||
setVolume(newVolume);
|
setVolume(newVolume);
|
||||||
if (isEnabled) {
|
if (isEnabled) {
|
||||||
// Test sound with new volume
|
|
||||||
setTimeout(() => playSound(), 100);
|
setTimeout(() => playSound(), 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -46,7 +43,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
|||||||
const newEnabled = !isEnabled;
|
const newEnabled = !isEnabled;
|
||||||
setIsEnabled(newEnabled);
|
setIsEnabled(newEnabled);
|
||||||
if (newEnabled) {
|
if (newEnabled) {
|
||||||
// Test sound when enabling
|
|
||||||
setTimeout(() => playSound(), 100);
|
setTimeout(() => playSound(), 100);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -59,7 +55,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-center gap-3 ${className}`}>
|
<div className={`flex items-center gap-3 ${className}`}>
|
||||||
{/* Sound Toggle */}
|
|
||||||
<button
|
<button
|
||||||
onClick={toggleSound}
|
onClick={toggleSound}
|
||||||
className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
className="p-2 rounded-lg hover:bg-gray-100 transition-colors"
|
||||||
@@ -68,7 +63,6 @@ export const SoundSettings: React.FC<SoundSettingsProps> = ({ className = '' })
|
|||||||
{getVolumeIcon()}
|
{getVolumeIcon()}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{/* Volume Slider */}
|
|
||||||
{isEnabled && (
|
{isEnabled && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -35,21 +35,17 @@ const Table = <T extends RowDataType>({
|
|||||||
const longPressTimer = useRef<number | null>(null);
|
const longPressTimer = useRef<number | null>(null);
|
||||||
const [longPressActiveId, setLongPressActiveId] = useState<string | number | null>(null);
|
const [longPressActiveId, setLongPressActiveId] = useState<string | number | null>(null);
|
||||||
|
|
||||||
// استفاده از selectedRows خارجی اگر موجود باشد، در غیر این صورت از internal استفاده کن
|
|
||||||
const selectedRows = externalSelectedRows !== undefined ? externalSelectedRows : internalSelectedRows;
|
const selectedRows = externalSelectedRows !== undefined ? externalSelectedRows : internalSelectedRows;
|
||||||
|
|
||||||
// پاک کردن انتخابها وقتی clearSelection تغییر کند
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (clearSelection && externalSelectedRows === undefined) {
|
if (clearSelection && externalSelectedRows === undefined) {
|
||||||
setInternalSelectedRows([]);
|
setInternalSelectedRows([]);
|
||||||
}
|
}
|
||||||
// خروج از حالت انتخاب موبایل هم
|
|
||||||
if (clearSelection) {
|
if (clearSelection) {
|
||||||
setMobileSelectionMode(false);
|
setMobileSelectionMode(false);
|
||||||
}
|
}
|
||||||
}, [clearSelection, externalSelectedRows]);
|
}, [clearSelection, externalSelectedRows]);
|
||||||
|
|
||||||
// تمیز کردن timer هنگام unmount
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (longPressTimer.current) {
|
if (longPressTimer.current) {
|
||||||
@@ -58,7 +54,6 @@ const Table = <T extends RowDataType>({
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// خروج از حالت انتخاب اگر هیچ آیتمی انتخاب نشده، ورود اگر آیتمی انتخاب شده
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (selectedRows.length === 0 && mobileSelectionMode) {
|
if (selectedRows.length === 0 && mobileSelectionMode) {
|
||||||
setMobileSelectionMode(false);
|
setMobileSelectionMode(false);
|
||||||
@@ -73,10 +68,9 @@ const Table = <T extends RowDataType>({
|
|||||||
setLongPressActiveId(item.id);
|
setLongPressActiveId(item.id);
|
||||||
longPressTimer.current = window.setTimeout(() => {
|
longPressTimer.current = window.setTimeout(() => {
|
||||||
setMobileSelectionMode(true);
|
setMobileSelectionMode(true);
|
||||||
// انتخاب آیتم فعلی
|
|
||||||
handleSingleRowSelect(item, true);
|
handleSingleRowSelect(item, true);
|
||||||
setLongPressActiveId(null);
|
setLongPressActiveId(null);
|
||||||
}, 500); // 500ms برای long press
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLongPressEnd = () => {
|
const handleLongPressEnd = () => {
|
||||||
@@ -89,11 +83,9 @@ const Table = <T extends RowDataType>({
|
|||||||
|
|
||||||
const handleMobileRowClick = (item: T) => {
|
const handleMobileRowClick = (item: T) => {
|
||||||
if (mobileSelectionMode) {
|
if (mobileSelectionMode) {
|
||||||
// در حالت انتخاب، آیتم را انتخاب/عدم انتخاب کن
|
|
||||||
const isSelected = isRowSelected(item);
|
const isSelected = isRowSelected(item);
|
||||||
handleSingleRowSelect(item, !isSelected);
|
handleSingleRowSelect(item, !isSelected);
|
||||||
} else if (longPressActiveId !== item.id) {
|
} else if (longPressActiveId !== item.id) {
|
||||||
// در حالت عادی، row click عادی را اجرا کن
|
|
||||||
handleRowClick(item);
|
handleRowClick(item);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -120,12 +112,10 @@ const Table = <T extends RowDataType>({
|
|||||||
const newSelectedRows = selectedRows.length === data.length ? [] : [...data];
|
const newSelectedRows = selectedRows.length === data.length ? [] : [...data];
|
||||||
|
|
||||||
if (externalSelectedRows !== undefined) {
|
if (externalSelectedRows !== undefined) {
|
||||||
// اگر selectedRows از خارج کنترل میشود، فقط callback را فراخوانی کن
|
|
||||||
if (onSelectionChange) {
|
if (onSelectionChange) {
|
||||||
onSelectionChange(newSelectedRows);
|
onSelectionChange(newSelectedRows);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// در غیر این صورت internal state را بهروزرسانی کن
|
|
||||||
setInternalSelectedRows(newSelectedRows);
|
setInternalSelectedRows(newSelectedRows);
|
||||||
if (onSelectionChange) {
|
if (onSelectionChange) {
|
||||||
onSelectionChange(newSelectedRows);
|
onSelectionChange(newSelectedRows);
|
||||||
@@ -139,12 +129,10 @@ const Table = <T extends RowDataType>({
|
|||||||
: selectedRows.filter((row) => row.id !== item.id);
|
: selectedRows.filter((row) => row.id !== item.id);
|
||||||
|
|
||||||
if (externalSelectedRows !== undefined) {
|
if (externalSelectedRows !== undefined) {
|
||||||
// اگر selectedRows از خارج کنترل میشود، فقط callback را فراخوانی کن
|
|
||||||
if (onSelectionChange) {
|
if (onSelectionChange) {
|
||||||
onSelectionChange(newSelectedRows);
|
onSelectionChange(newSelectedRows);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// در غیر این صورت internal state را بهروزرسانی کن
|
|
||||||
setInternalSelectedRows(newSelectedRows);
|
setInternalSelectedRows(newSelectedRows);
|
||||||
if (onSelectionChange) {
|
if (onSelectionChange) {
|
||||||
onSelectionChange(newSelectedRows);
|
onSelectionChange(newSelectedRows);
|
||||||
@@ -156,7 +144,6 @@ const Table = <T extends RowDataType>({
|
|||||||
return selectedRows.some((row) => row.id === item.id);
|
return selectedRows.some((row) => row.id === item.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Gmail-style layout برای وقتی header نمایش داده نمیشود
|
|
||||||
const renderGmailStyleRows = () => {
|
const renderGmailStyleRows = () => {
|
||||||
const hasActions = (actionsPosition === 'top' || actionsPosition === 'above-header') && (actions || (selectable && selectedRows.length > 0 && selectedActions));
|
const hasActions = (actionsPosition === 'top' || actionsPosition === 'above-header') && (actions || (selectable && selectedRows.length > 0 && selectedActions));
|
||||||
const roundedClass = hasActions ? 'rounded-b-2xl' : 'rounded-2xl';
|
const roundedClass = hasActions ? 'rounded-b-2xl' : 'rounded-2xl';
|
||||||
@@ -183,7 +170,6 @@ const Table = <T extends RowDataType>({
|
|||||||
return (
|
return (
|
||||||
<div className={`bg-white ${roundedClass} overflow-hidden`}>
|
<div className={`bg-white ${roundedClass} overflow-hidden`}>
|
||||||
{data.map((item, rowIndex) => {
|
{data.map((item, rowIndex) => {
|
||||||
// Gmail-style: خوانده نشده = پسزمینه سفید، خوانده شده = پسزمینه خاکستری
|
|
||||||
const isUnread = 'seen' in item && !item.seen;
|
const isUnread = 'seen' in item && !item.seen;
|
||||||
const bgColor = isUnread ? 'bg-white' : 'bg-gray-50';
|
const bgColor = isUnread ? 'bg-white' : 'bg-gray-50';
|
||||||
const hoverColor = isUnread ? 'hover:bg-gray-50' : 'hover:bg-gray-100';
|
const hoverColor = isUnread ? 'hover:bg-gray-50' : 'hover:bg-gray-100';
|
||||||
@@ -213,16 +199,11 @@ const Table = <T extends RowDataType>({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Inline Actions - بعد از checkbox */}
|
|
||||||
|
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
{/* محتوای اصلی - ستون اول */}
|
|
||||||
<div className={`flex items-center mb-0.5 text-sm ${fontWeight}`}>
|
<div className={`flex items-center mb-0.5 text-sm ${fontWeight}`}>
|
||||||
{columns[0]?.render ? columns[0].render(item) : (item[columns[0]?.key] as React.ReactNode)}
|
{columns[0]?.render ? columns[0].render(item) : (item[columns[0]?.key] as React.ReactNode)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* اطلاعات اضافی - سه ستون آخر در یک خط */}
|
|
||||||
{columns.length > 1 && (
|
{columns.length > 1 && (
|
||||||
<div className={`flex items-center gap-3 text-xs text-gray-500 ${fontWeight}`}>
|
<div className={`flex items-center gap-3 text-xs text-gray-500 ${fontWeight}`}>
|
||||||
{columns.slice(1).map((col) => (
|
{columns.slice(1).map((col) => (
|
||||||
@@ -269,7 +250,6 @@ const Table = <T extends RowDataType>({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return data.map((item, rowIndex) => {
|
return data.map((item, rowIndex) => {
|
||||||
// Gmail-style: خوانده نشده = پسزمینه سفید، خوانده شده = پسزمینه خاکستری
|
|
||||||
const isUnread = 'seen' in item && !item.seen;
|
const isUnread = 'seen' in item && !item.seen;
|
||||||
const bgColor = isUnread ? 'bg-white' : 'bg-gray-50';
|
const bgColor = isUnread ? 'bg-white' : 'bg-gray-50';
|
||||||
const hoverColor = isUnread ? 'hover:bg-gray-50' : 'hover:bg-gray-100';
|
const hoverColor = isUnread ? 'hover:bg-gray-50' : 'hover:bg-gray-100';
|
||||||
@@ -378,11 +358,9 @@ const Table = <T extends RowDataType>({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// اگر header نشان داده نمیشود، از Gmail-style layout فقط در موبایل استفاده کن
|
|
||||||
if (!showHeader) {
|
if (!showHeader) {
|
||||||
return (
|
return (
|
||||||
<div className={`relative mt-6 md:mt-9 w-full ${className}`}>
|
<div className={`relative mt-6 md:mt-9 w-full ${className}`}>
|
||||||
{/* نسخه موبایل - Gmail style */}
|
|
||||||
<div className={`md:hidden`}>
|
<div className={`md:hidden`}>
|
||||||
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
|
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
|
||||||
<div className="h-[64px] bg-white flex items-center justify-between px-4 rounded-t-2xl border-b border-[#EAEDF5]">
|
<div className="h-[64px] bg-white flex items-center justify-between px-4 rounded-t-2xl border-b border-[#EAEDF5]">
|
||||||
@@ -394,7 +372,6 @@ const Table = <T extends RowDataType>({
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setMobileSelectionMode(false);
|
setMobileSelectionMode(false);
|
||||||
// پاک کردن انتخابها
|
|
||||||
if (externalSelectedRows !== undefined) {
|
if (externalSelectedRows !== undefined) {
|
||||||
if (onSelectionChange) {
|
if (onSelectionChange) {
|
||||||
onSelectionChange([]);
|
onSelectionChange([]);
|
||||||
@@ -416,7 +393,6 @@ const Table = <T extends RowDataType>({
|
|||||||
{renderGmailStyleRows()}
|
{renderGmailStyleRows()}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* نسخه دسکتاپ - Table معمولی */}
|
|
||||||
<div className={`hidden md:block`}>
|
<div className={`hidden md:block`}>
|
||||||
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
|
{(actionsPosition === 'top' || actionsPosition === 'above-header') && (
|
||||||
<div className="h-[74px] bg-white flex items-center px-6 rounded-t-3xl">
|
<div className="h-[74px] bg-white flex items-center px-6 rounded-t-3xl">
|
||||||
@@ -509,7 +485,6 @@ const MyComponent = () => {
|
|||||||
|
|
||||||
const handlePageChange = (page: number) => {
|
const handlePageChange = (page: number) => {
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
// اینجا میتوانید API call جدید برای دریافت دادههای صفحه جدید بزنید
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -13,24 +13,20 @@ let addToast: (toast: Toast) => void;
|
|||||||
const ToastContainer: React.FC = () => {
|
const ToastContainer: React.FC = () => {
|
||||||
const [toasts, setToasts] = useState<Toast[]>([]);
|
const [toasts, setToasts] = useState<Toast[]>([]);
|
||||||
|
|
||||||
// ثبت toast جدید
|
|
||||||
const showToast = (toast: Toast) => {
|
const showToast = (toast: Toast) => {
|
||||||
setToasts((prev) => [...prev, toast]);
|
setToasts((prev) => [...prev, toast]);
|
||||||
|
|
||||||
// حذف خودکار بعد از ۳ ثانیه
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setToasts((prev) => prev.map(t =>
|
setToasts((prev) => prev.map(t =>
|
||||||
t.id === toast.id ? { ...t, isExiting: true } : t
|
t.id === toast.id ? { ...t, isExiting: true } : t
|
||||||
));
|
));
|
||||||
|
|
||||||
// حذف از DOM بعد از اتمام انیمیشن
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setToasts((prev) => prev.filter((t) => t.id !== toast.id));
|
setToasts((prev) => prev.filter((t) => t.id !== toast.id));
|
||||||
}, 300); // مدت زمان انیمیشن خروج
|
}, 300);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
};
|
};
|
||||||
|
|
||||||
// تخصیص تابع نمایش toast به متغیر سراسری
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
addToast = showToast;
|
addToast = showToast;
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ const EmailInput: FC<EmailInputProps> = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always search via API for fresh results
|
|
||||||
if (onSearchSuggestions) {
|
if (onSearchSuggestions) {
|
||||||
if (searchTimeoutRef.current) {
|
if (searchTimeoutRef.current) {
|
||||||
clearTimeout(searchTimeoutRef.current)
|
clearTimeout(searchTimeoutRef.current)
|
||||||
@@ -51,7 +50,6 @@ const EmailInput: FC<EmailInputProps> = ({
|
|||||||
setSelectedSuggestionIndex(-1)
|
setSelectedSuggestionIndex(-1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update filtered suggestions when API suggestions change
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (suggestions.length > 0) {
|
if (suggestions.length > 0) {
|
||||||
const filtered = suggestions
|
const filtered = suggestions
|
||||||
@@ -63,7 +61,6 @@ const EmailInput: FC<EmailInputProps> = ({
|
|||||||
setFilteredSuggestions(filtered)
|
setFilteredSuggestions(filtered)
|
||||||
setShowSuggestions(filtered.length > 0 && to.trim().length > 0)
|
setShowSuggestions(filtered.length > 0 && to.trim().length > 0)
|
||||||
} else if (to.trim() === '') {
|
} else if (to.trim() === '') {
|
||||||
// Clear suggestions when input is empty
|
|
||||||
setFilteredSuggestions([])
|
setFilteredSuggestions([])
|
||||||
setShowSuggestions(false)
|
setShowSuggestions(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,35 +20,29 @@ export const useNewMessage = () => {
|
|||||||
} = useSharedStore();
|
} = useSharedStore();
|
||||||
const { email: userEmail } = useAuthStore();
|
const { email: userEmail } = useAuthStore();
|
||||||
|
|
||||||
// Form state
|
|
||||||
const [toEmails, setToEmails] = useState<string[]>([]);
|
const [toEmails, setToEmails] = useState<string[]>([]);
|
||||||
const [subject, setSubject] = useState("");
|
const [subject, setSubject] = useState("");
|
||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState("");
|
||||||
const [priority, setPriority] = useState("");
|
const [priority, setPriority] = useState("");
|
||||||
const [attachments, setAttachments] = useState<EmailAttachmentDto[]>([]);
|
const [attachments, setAttachments] = useState<EmailAttachmentDto[]>([]);
|
||||||
|
|
||||||
// Loading states
|
|
||||||
const [isSending, setIsSending] = useState(false);
|
const [isSending, setIsSending] = useState(false);
|
||||||
const [isSavingDraft, setIsSavingDraft] = useState(false);
|
const [isSavingDraft, setIsSavingDraft] = useState(false);
|
||||||
|
|
||||||
// API hooks
|
|
||||||
const sendEmailMutation = useSendEmail();
|
const sendEmailMutation = useSendEmail();
|
||||||
const updateDraftMutation = useUpdateDraft();
|
const updateDraftMutation = useUpdateDraft();
|
||||||
const sendDraftMutation = useSendDraft();
|
const sendDraftMutation = useSendDraft();
|
||||||
|
|
||||||
// Email suggestions
|
|
||||||
const {
|
const {
|
||||||
suggestions: emailSuggestions,
|
suggestions: emailSuggestions,
|
||||||
searchSuggestions,
|
searchSuggestions,
|
||||||
} = useEmailSuggestions();
|
} = useEmailSuggestions();
|
||||||
|
|
||||||
// Get draft detail if draftData exists
|
|
||||||
const { data: draftDetail } = useGetMessageDetail(
|
const { data: draftDetail } = useGetMessageDetail(
|
||||||
draftData?.id.toString() || "",
|
draftData?.id.toString() || "",
|
||||||
draftData?.mailbox || ""
|
draftData?.mailbox || ""
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load draft data into form
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (draftDetail) {
|
if (draftDetail) {
|
||||||
const toEmailsList =
|
const toEmailsList =
|
||||||
@@ -81,7 +75,6 @@ export const useNewMessage = () => {
|
|||||||
return emailRegex.test(email);
|
return emailRegex.test(email);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to search email suggestions
|
|
||||||
const searchEmailSuggestions = (query: string) => {
|
const searchEmailSuggestions = (query: string) => {
|
||||||
searchSuggestions(query);
|
searchSuggestions(query);
|
||||||
};
|
};
|
||||||
@@ -237,7 +230,6 @@ export const useNewMessage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Form state
|
|
||||||
toEmails,
|
toEmails,
|
||||||
subject,
|
subject,
|
||||||
content,
|
content,
|
||||||
@@ -245,11 +237,9 @@ export const useNewMessage = () => {
|
|||||||
attachments,
|
attachments,
|
||||||
emailSuggestions,
|
emailSuggestions,
|
||||||
|
|
||||||
// Loading states
|
|
||||||
isSending,
|
isSending,
|
||||||
isSavingDraft,
|
isSavingDraft,
|
||||||
|
|
||||||
// Form handlers
|
|
||||||
addEmail,
|
addEmail,
|
||||||
removeEmail,
|
removeEmail,
|
||||||
setSubject,
|
setSubject,
|
||||||
@@ -257,10 +247,8 @@ export const useNewMessage = () => {
|
|||||||
setPriority,
|
setPriority,
|
||||||
setAttachments,
|
setAttachments,
|
||||||
|
|
||||||
// Search functions
|
|
||||||
searchEmailSuggestions,
|
searchEmailSuggestions,
|
||||||
|
|
||||||
// Actions
|
|
||||||
handleSend,
|
handleSend,
|
||||||
handleSaveDraft,
|
handleSaveDraft,
|
||||||
resetForm,
|
resetForm,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export const useReply = ({
|
|||||||
const [content, setContent] = useState<string>("");
|
const [content, setContent] = useState<string>("");
|
||||||
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
const [isExpanded, setIsExpanded] = useState<boolean>(false);
|
||||||
|
|
||||||
// API hooks
|
|
||||||
const sendEmailMutation = useSendEmail();
|
const sendEmailMutation = useSendEmail();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
@@ -39,7 +38,6 @@ export const useReply = ({
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format date to Persian
|
|
||||||
const originalDate = new Date(originalMessage.date);
|
const originalDate = new Date(originalMessage.date);
|
||||||
const persianDate = new Intl.DateTimeFormat("fa-IR", {
|
const persianDate = new Intl.DateTimeFormat("fa-IR", {
|
||||||
weekday: "long",
|
weekday: "long",
|
||||||
@@ -143,12 +141,10 @@ ${
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// State
|
|
||||||
content,
|
content,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
isSending: sendEmailMutation.isPending,
|
isSending: sendEmailMutation.isPending,
|
||||||
|
|
||||||
// Handlers
|
|
||||||
setContent,
|
setContent,
|
||||||
expandReply,
|
expandReply,
|
||||||
collapseReply,
|
collapseReply,
|
||||||
@@ -156,7 +152,6 @@ ${
|
|||||||
handleSaveDraft,
|
handleSaveDraft,
|
||||||
resetForm,
|
resetForm,
|
||||||
|
|
||||||
// Original message data
|
|
||||||
originalMessage,
|
originalMessage,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+4
-23
@@ -41,7 +41,7 @@ export const removeRefreshToken = async () => {
|
|||||||
export const timeAgo = (date: string | Date): string => {
|
export const timeAgo = (date: string | Date): string => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const past = new Date(date);
|
const past = new Date(date);
|
||||||
const diff = Math.floor((now.getTime() - past.getTime()) / 1000); // اختلاف بر حسب ثانیه
|
const diff = Math.floor((now.getTime() - past.getTime()) / 1000);
|
||||||
|
|
||||||
const units = [
|
const units = [
|
||||||
{ name: "سال", value: 60 * 60 * 24 * 365 },
|
{ name: "سال", value: 60 * 60 * 24 * 365 },
|
||||||
@@ -82,48 +82,33 @@ export const formatDate = (dateString: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const detectTextDirection = (text: string): "ltr" | "rtl" => {
|
export const detectTextDirection = (text: string): "ltr" | "rtl" => {
|
||||||
// حذف تگهای HTML برای تحلیل فقط متن
|
|
||||||
const plainText = text.replace(/<[^>]*>/g, " ").trim();
|
const plainText = text.replace(/<[^>]*>/g, " ").trim();
|
||||||
|
|
||||||
// اگر متن خالی است، از RTL استفاده کن
|
|
||||||
if (!plainText) return "rtl";
|
if (!plainText) return "rtl";
|
||||||
|
|
||||||
// الگوی کاراکترهای انگلیسی و اعداد
|
|
||||||
const englishPattern = /[a-zA-Z0-9\s.,!?;:'"()\-@#$%^&*+=<>{}[\]\\|`~_]/g;
|
const englishPattern = /[a-zA-Z0-9\s.,!?;:'"()\-@#$%^&*+=<>{}[\]\\|`~_]/g;
|
||||||
const englishMatches = plainText.match(englishPattern) || [];
|
const englishMatches = plainText.match(englishPattern) || [];
|
||||||
|
|
||||||
// الگوی کاراکترهای فارسی و عربی (برای آینده ممکن است نیاز باشد)
|
|
||||||
// const persianArabicPattern = /[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]/g;
|
|
||||||
|
|
||||||
// محاسبه درصد کاراکترهای انگلیسی
|
|
||||||
const totalChars = plainText.replace(/\s/g, "").length;
|
const totalChars = plainText.replace(/\s/g, "").length;
|
||||||
const englishChars = englishMatches.join("").replace(/\s/g, "").length;
|
const englishChars = englishMatches.join("").replace(/\s/g, "").length;
|
||||||
const englishPercentage =
|
const englishPercentage =
|
||||||
totalChars > 0 ? (englishChars / totalChars) * 100 : 0;
|
totalChars > 0 ? (englishChars / totalChars) * 100 : 0;
|
||||||
|
|
||||||
// اگر بیش از 70% متن انگلیسی است، LTR استفاده کن
|
|
||||||
return englishPercentage > 70 ? "ltr" : "rtl";
|
return englishPercentage > 70 ? "ltr" : "rtl";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sanitizeEmailHTML = (html: string): string => {
|
export const sanitizeEmailHTML = (html: string): string => {
|
||||||
try {
|
try {
|
||||||
// Remove style tags and their content
|
|
||||||
let sanitized = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
let sanitized = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
||||||
|
|
||||||
// Remove link tags (CSS files)
|
|
||||||
sanitized = sanitized.replace(/<link[^>]*>/gi, "");
|
sanitized = sanitized.replace(/<link[^>]*>/gi, "");
|
||||||
|
|
||||||
// Remove script tags for security
|
|
||||||
sanitized = sanitized.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
sanitized = sanitized.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
||||||
|
|
||||||
// Remove potentially harmful attributes
|
|
||||||
sanitized = sanitized.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, "");
|
sanitized = sanitized.replace(/\s*on\w+\s*=\s*["'][^"']*["']/gi, "");
|
||||||
|
|
||||||
// تشخیص جهت متن اصلی
|
|
||||||
const detectedDirection = detectTextDirection(sanitized);
|
const detectedDirection = detectTextDirection(sanitized);
|
||||||
|
|
||||||
// فقط font-family، font-size و font-weight رو از inline styles حذف کن
|
|
||||||
// text-align و direction رو فقط اگر با جهت تشخیص داده شده مطابقت نداشته باشد حذف کن
|
|
||||||
sanitized = sanitized.replace(
|
sanitized = sanitized.replace(
|
||||||
/style\s*=\s*["']([^"']*?)["']/gi,
|
/style\s*=\s*["']([^"']*?)["']/gi,
|
||||||
(_, styles) => {
|
(_, styles) => {
|
||||||
@@ -132,19 +117,17 @@ export const sanitizeEmailHTML = (html: string): string => {
|
|||||||
.replace(/font-size\s*:\s*[^;]*;?/gi, "")
|
.replace(/font-size\s*:\s*[^;]*;?/gi, "")
|
||||||
.replace(/font-weight\s*:\s*[^;]*;?/gi, "");
|
.replace(/font-weight\s*:\s*[^;]*;?/gi, "");
|
||||||
|
|
||||||
// اگر جهت تشخیص داده شده LTR است، direction و text-align را حفظ کن
|
|
||||||
if (detectedDirection === "ltr") {
|
if (detectedDirection === "ltr") {
|
||||||
// direction و text-align را حفظ کن
|
// TODO: Implement LTR
|
||||||
} else {
|
} else {
|
||||||
// اگر RTL است، این استایلها را حذف کن تا استایل سایت اعمال شود
|
|
||||||
cleanedStyles = cleanedStyles
|
cleanedStyles = cleanedStyles
|
||||||
.replace(/text-align\s*:\s*[^;]*;?/gi, "")
|
.replace(/text-align\s*:\s*[^;]*;?/gi, "")
|
||||||
.replace(/direction\s*:\s*[^;]*;?/gi, "");
|
.replace(/direction\s*:\s*[^;]*;?/gi, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanedStyles = cleanedStyles
|
cleanedStyles = cleanedStyles
|
||||||
.replace(/;\s*;/g, ";") // حذف سمیکالنهای اضافی
|
.replace(/;\s*;/g, ";")
|
||||||
.replace(/^\s*;\s*|\s*;\s*$/g, ""); // حذف سمیکالنهای ابتدا و انتها
|
.replace(/^\s*;\s*|\s*;\s*$/g, "");
|
||||||
|
|
||||||
return cleanedStyles ? `style="${cleanedStyles}"` : "";
|
return cleanedStyles ? `style="${cleanedStyles}"` : "";
|
||||||
}
|
}
|
||||||
@@ -157,7 +140,6 @@ export const sanitizeEmailHTML = (html: string): string => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Interface برای mailbox structure
|
|
||||||
interface MailboxItem {
|
interface MailboxItem {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -165,7 +147,6 @@ interface MailboxItem {
|
|||||||
total: number;
|
total: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility function برای گرفتن mailbox id از localStorage
|
|
||||||
export const getMailboxId = (mailboxName: string): string | null => {
|
export const getMailboxId = (mailboxName: string): string | null => {
|
||||||
try {
|
try {
|
||||||
const mailboxesStr = localStorage.getItem("mailboxes");
|
const mailboxesStr = localStorage.getItem("mailboxes");
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ import { toast } from '../components/Toast';
|
|||||||
export const useEmailActions = (customRefetch?: () => void) => {
|
export const useEmailActions = (customRefetch?: () => void) => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
// تمام query key های موجود در سیستم
|
|
||||||
const invalidateAllQueries = () => {
|
const invalidateAllQueries = () => {
|
||||||
// صفحههای اصلی
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['inbox'] });
|
queryClient.invalidateQueries({ queryKey: ['inbox'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['drafts'] });
|
queryClient.invalidateQueries({ queryKey: ['drafts'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['archive-messages'] });
|
queryClient.invalidateQueries({ queryKey: ['archive-messages'] });
|
||||||
@@ -31,11 +29,9 @@ export const useEmailActions = (customRefetch?: () => void) => {
|
|||||||
queryClient.invalidateQueries({ queryKey: ['trash-messages'] });
|
queryClient.invalidateQueries({ queryKey: ['trash-messages'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['search-messages'] });
|
queryClient.invalidateQueries({ queryKey: ['search-messages'] });
|
||||||
|
|
||||||
// پیامهای تکی
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['message'] });
|
queryClient.invalidateQueries({ queryKey: ['message'] });
|
||||||
queryClient.invalidateQueries({ queryKey: ['draft'] });
|
queryClient.invalidateQueries({ queryKey: ['draft'] });
|
||||||
|
|
||||||
// صفحههایی که از React Query استفاده نمیکنند (مثل sent)
|
|
||||||
if (customRefetch) {
|
if (customRefetch) {
|
||||||
customRefetch();
|
customRefetch();
|
||||||
}
|
}
|
||||||
@@ -162,7 +158,6 @@ export const useEmailActions = (customRefetch?: () => void) => {
|
|||||||
restoreMutation.isPending;
|
restoreMutation.isPending;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// Bulk actions
|
|
||||||
bulkAction: bulkActionMutation.mutate,
|
bulkAction: bulkActionMutation.mutate,
|
||||||
bulkMarkAsSeen: (messageIds: number[], mailbox: string) =>
|
bulkMarkAsSeen: (messageIds: number[], mailbox: string) =>
|
||||||
bulkActionMutation.mutate({ messageIds, action: BulkActionType.SEEN, mailbox: mailbox }),
|
bulkActionMutation.mutate({ messageIds, action: BulkActionType.SEEN, mailbox: mailbox }),
|
||||||
@@ -183,7 +178,6 @@ export const useEmailActions = (customRefetch?: () => void) => {
|
|||||||
bulkRestore: (messageIds: number[], mailbox: string) =>
|
bulkRestore: (messageIds: number[], mailbox: string) =>
|
||||||
bulkActionMutation.mutate({ messageIds, action: BulkActionType.RESTORE, mailbox: mailbox }),
|
bulkActionMutation.mutate({ messageIds, action: BulkActionType.RESTORE, mailbox: mailbox }),
|
||||||
|
|
||||||
// Single actions (keeping for backward compatibility)
|
|
||||||
markAsSeen: markAsSeenMutation.mutate,
|
markAsSeen: markAsSeenMutation.mutate,
|
||||||
archive: archiveMutation.mutate,
|
archive: archiveMutation.mutate,
|
||||||
favorite: favoriteMutation.mutate,
|
favorite: favoriteMutation.mutate,
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export const useEmailEvents = (
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!socket) return;
|
if (!socket) return;
|
||||||
|
|
||||||
// Register event handlers
|
|
||||||
if (handlers.onNewEmail) {
|
if (handlers.onNewEmail) {
|
||||||
socket.on("new_email", handlers.onNewEmail);
|
socket.on("new_email", handlers.onNewEmail);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export const useEmailWebSocket = ({
|
|||||||
transports: ["websocket", "polling"],
|
transports: ["websocket", "polling"],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Connection events
|
|
||||||
newSocket.on("connect", () => {
|
newSocket.on("connect", () => {
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
onConnect?.();
|
onConnect?.();
|
||||||
@@ -43,7 +42,6 @@ export const useEmailWebSocket = ({
|
|||||||
onError?.(error.error);
|
onError?.(error.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Health monitoring
|
|
||||||
newSocket.on("ping", () => {
|
newSocket.on("ping", () => {
|
||||||
newSocket.emit("pong", { timestamp: new Date().toISOString() });
|
newSocket.emit("pong", { timestamp: new Date().toISOString() });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ const useDetectLatinAndNumbers = () => {
|
|||||||
if (node.nodeType === Node.TEXT_NODE) {
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
const textContent = node.nodeValue;
|
const textContent = node.nodeValue;
|
||||||
if (textContent) {
|
if (textContent) {
|
||||||
// اگر عدد داشت
|
|
||||||
if (/\d/.test(textContent)) {
|
if (/\d/.test(textContent)) {
|
||||||
element.classList.add("has-number");
|
element.classList.add("has-number");
|
||||||
}
|
}
|
||||||
// اگر حرف لاتین داشت
|
|
||||||
if (/[a-zA-Z]/.test(textContent)) {
|
if (/[a-zA-Z]/.test(textContent)) {
|
||||||
element.classList.add("has-latin");
|
element.classList.add("has-latin");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ const List: FC = () => {
|
|||||||
const [selectedMessages, setSelectedMessages] = useState<TrashMessage[]>([]);
|
const [selectedMessages, setSelectedMessages] = useState<TrashMessage[]>([]);
|
||||||
const [selectAll, setSelectAll] = useState(false);
|
const [selectAll, setSelectAll] = useState(false);
|
||||||
|
|
||||||
// تابع برای گرفتن فقط ساعت
|
|
||||||
const formatTime = (dateString: string) => {
|
const formatTime = (dateString: string) => {
|
||||||
try {
|
try {
|
||||||
if (!dateString) return '--:--';
|
if (!dateString) return '--:--';
|
||||||
@@ -49,7 +48,6 @@ const List: FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ستونهای دسکتاپ
|
|
||||||
const desktopColumns: ColumnType<TrashMessage>[] = [
|
const desktopColumns: ColumnType<TrashMessage>[] = [
|
||||||
{
|
{
|
||||||
key: 'subject',
|
key: 'subject',
|
||||||
@@ -78,7 +76,6 @@ const List: FC = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// ستونهای موبایل (سه خطی)
|
|
||||||
const mobileColumns: ColumnType<TrashMessage>[] = [
|
const mobileColumns: ColumnType<TrashMessage>[] = [
|
||||||
{
|
{
|
||||||
key: 'content',
|
key: 'content',
|
||||||
@@ -106,7 +103,6 @@ const List: FC = () => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// انتخاب ستونها بر اساس سایز صفحه
|
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -170,7 +166,6 @@ const List: FC = () => {
|
|||||||
|
|
||||||
const handleSelectionChange = (selectedRows: TrashMessage[]) => {
|
const handleSelectionChange = (selectedRows: TrashMessage[]) => {
|
||||||
setSelectedMessages(selectedRows);
|
setSelectedMessages(selectedRows);
|
||||||
// تنظیم وضعیت select all بر اساس انتخاب شدهها
|
|
||||||
const messages = trashData?.data?.trashMails || [];
|
const messages = trashData?.data?.trashMails || [];
|
||||||
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
|
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const getTrashMessages = async (
|
|||||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||||
const url = isSearch ? "search" : "messages/trash";
|
const url = isSearch ? "search" : "messages/trash";
|
||||||
|
|
||||||
// اضافه کردن mailbox id از localStorage
|
|
||||||
const mailboxId = getMailboxId("Trash");
|
const mailboxId = getMailboxId("Trash");
|
||||||
const queryWithMailbox = {
|
const queryWithMailbox = {
|
||||||
...query,
|
...query,
|
||||||
@@ -23,8 +22,6 @@ export const getTrashMessages = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Note: deleteMessagePermanently and restoreMessage are now handled by EmailActionsService
|
|
||||||
|
|
||||||
export const emptyTrash = async () => {
|
export const emptyTrash = async () => {
|
||||||
const mailboxId = getMailboxId(MailboxEnum.TRASH);
|
const mailboxId = getMailboxId(MailboxEnum.TRASH);
|
||||||
const { data } = await axios.patch(
|
const { data } = await axios.patch(
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ const List: FC = () => {
|
|||||||
const [selectedMessages, setSelectedMessages] = useState<SearchMessage[]>([]);
|
const [selectedMessages, setSelectedMessages] = useState<SearchMessage[]>([]);
|
||||||
const [selectAll, setSelectAll] = useState(false);
|
const [selectAll, setSelectAll] = useState(false);
|
||||||
|
|
||||||
// تابع برای گرفتن فقط ساعت
|
|
||||||
const formatTime = (dateString: string) => {
|
const formatTime = (dateString: string) => {
|
||||||
try {
|
try {
|
||||||
if (!dateString) return '--:--';
|
if (!dateString) return '--:--';
|
||||||
@@ -45,7 +44,6 @@ const List: FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ستونهای دسکتاپ
|
|
||||||
const desktopColumns: ColumnType<SearchMessage>[] = [
|
const desktopColumns: ColumnType<SearchMessage>[] = [
|
||||||
{
|
{
|
||||||
key: 'subject',
|
key: 'subject',
|
||||||
@@ -81,7 +79,6 @@ const List: FC = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// ستونهای موبایل (سه خطی)
|
|
||||||
const mobileColumns: ColumnType<SearchMessage>[] = [
|
const mobileColumns: ColumnType<SearchMessage>[] = [
|
||||||
{
|
{
|
||||||
key: 'content',
|
key: 'content',
|
||||||
@@ -112,7 +109,6 @@ const List: FC = () => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// انتخاب ستونها بر اساس سایز صفحه
|
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -171,7 +167,6 @@ const List: FC = () => {
|
|||||||
|
|
||||||
const handleSelectionChange = (selectedRows: SearchMessage[]) => {
|
const handleSelectionChange = (selectedRows: SearchMessage[]) => {
|
||||||
setSelectedMessages(selectedRows);
|
setSelectedMessages(selectedRows);
|
||||||
// تنظیم وضعیت select all بر اساس انتخاب شدهها
|
|
||||||
const messages = searchData?.data?.results || [];
|
const messages = searchData?.data?.results || [];
|
||||||
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
|
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ const List: FC = () => {
|
|||||||
const [selectedMessages, setSelectedMessages] = useState<InboxMessage[]>([]);
|
const [selectedMessages, setSelectedMessages] = useState<InboxMessage[]>([]);
|
||||||
const [selectAll, setSelectAll] = useState(false);
|
const [selectAll, setSelectAll] = useState(false);
|
||||||
|
|
||||||
// تابع برای گرفتن فقط ساعت
|
|
||||||
const formatTime = (dateString: string) => {
|
const formatTime = (dateString: string) => {
|
||||||
try {
|
try {
|
||||||
if (!dateString) return '--:--';
|
if (!dateString) return '--:--';
|
||||||
@@ -46,7 +45,6 @@ const List: FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ستونهای دسکتاپ
|
|
||||||
const desktopColumns: ColumnType<InboxMessage>[] = [
|
const desktopColumns: ColumnType<InboxMessage>[] = [
|
||||||
{
|
{
|
||||||
key: 'subject',
|
key: 'subject',
|
||||||
@@ -89,7 +87,6 @@ const List: FC = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// ستونهای موبایل (سه خطی)
|
|
||||||
const mobileColumns: ColumnType<InboxMessage>[] = [
|
const mobileColumns: ColumnType<InboxMessage>[] = [
|
||||||
{
|
{
|
||||||
key: 'content',
|
key: 'content',
|
||||||
@@ -117,7 +114,6 @@ const List: FC = () => {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
// انتخاب ستونها بر اساس سایز صفحه
|
|
||||||
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -179,7 +175,6 @@ const List: FC = () => {
|
|||||||
|
|
||||||
const handleSelectionChange = (selectedRows: InboxMessage[]) => {
|
const handleSelectionChange = (selectedRows: InboxMessage[]) => {
|
||||||
setSelectedMessages(selectedRows);
|
setSelectedMessages(selectedRows);
|
||||||
// تنظیم وضعیت select all بر اساس انتخاب شدهها
|
|
||||||
const messages = sentData?.data?.sentMails || [];
|
const messages = sentData?.data?.sentMails || [];
|
||||||
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
|
setSelectAll(selectedRows.length === messages.length && messages.length > 0);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export const getSentMessages = async (
|
|||||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||||
const url = isSearch ? "search" : "messages/sent";
|
const url = isSearch ? "search" : "messages/sent";
|
||||||
|
|
||||||
// اضافه کردن mailbox id از localStorage
|
|
||||||
const mailboxId = getMailboxId("Sent Mail");
|
const mailboxId = getMailboxId("Sent Mail");
|
||||||
const queryWithMailbox = {
|
const queryWithMailbox = {
|
||||||
...query,
|
...query,
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export const getSpamMessages = async (
|
|||||||
!!query.search || !!query.datestart || query.dateend || query.from;
|
!!query.search || !!query.datestart || query.dateend || query.from;
|
||||||
const url = isSearch ? "search" : "messages/junk";
|
const url = isSearch ? "search" : "messages/junk";
|
||||||
|
|
||||||
// اضافه کردن mailbox id از localStorage
|
|
||||||
const mailboxId = getMailboxId("Junk");
|
const mailboxId = getMailboxId("Junk");
|
||||||
const queryWithMailbox = {
|
const queryWithMailbox = {
|
||||||
...query,
|
...query,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import axios from "../config/axios";
|
import axios from "../config/axios";
|
||||||
import { BulkActionRequest } from "../pages/received/types/Types";
|
import { BulkActionRequest } from "../pages/received/types/Types";
|
||||||
|
|
||||||
// Bulk action for multiple messages
|
|
||||||
export const bulkAction = async (request: BulkActionRequest) => {
|
export const bulkAction = async (request: BulkActionRequest) => {
|
||||||
const { data } = await axios.post(
|
const { data } = await axios.post(
|
||||||
`/email/messages/bulk-action?mailbox=${request.mailbox}`,
|
`/email/messages/bulk-action?mailbox=${request.mailbox}`,
|
||||||
@@ -10,7 +9,6 @@ export const bulkAction = async (request: BulkActionRequest) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mark message as seen
|
|
||||||
export const markMessageAsSeen = async (messageId: number, mailbox: string) => {
|
export const markMessageAsSeen = async (messageId: number, mailbox: string) => {
|
||||||
const { data } = await axios.patch(
|
const { data } = await axios.patch(
|
||||||
`/email/messages/${messageId}/seen?mailbox=${mailbox}`
|
`/email/messages/${messageId}/seen?mailbox=${mailbox}`
|
||||||
@@ -18,7 +16,6 @@ export const markMessageAsSeen = async (messageId: number, mailbox: string) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Move message to archive
|
|
||||||
export const moveMessageToArchive = async (
|
export const moveMessageToArchive = async (
|
||||||
messageId: number,
|
messageId: number,
|
||||||
mailbox: string
|
mailbox: string
|
||||||
@@ -29,7 +26,6 @@ export const moveMessageToArchive = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Move message to favorite
|
|
||||||
export const moveMessageToFavorite = async (
|
export const moveMessageToFavorite = async (
|
||||||
messageId: number,
|
messageId: number,
|
||||||
mailbox: string
|
mailbox: string
|
||||||
@@ -40,7 +36,6 @@ export const moveMessageToFavorite = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Move message to trash
|
|
||||||
export const moveMessageToTrash = async (
|
export const moveMessageToTrash = async (
|
||||||
messageId: number,
|
messageId: number,
|
||||||
mailbox: string
|
mailbox: string
|
||||||
@@ -51,7 +46,6 @@ export const moveMessageToTrash = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete message permanently (only for trash mailbox)
|
|
||||||
export const deleteMessagePermanently = async (
|
export const deleteMessagePermanently = async (
|
||||||
messageId: number,
|
messageId: number,
|
||||||
mailbox: string
|
mailbox: string
|
||||||
@@ -62,13 +56,11 @@ export const deleteMessagePermanently = async (
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send email
|
|
||||||
export const sendEmail = async (messageId: number) => {
|
export const sendEmail = async (messageId: number) => {
|
||||||
const { data } = await axios.post(`/email/send?messageId=${messageId}`);
|
const { data } = await axios.post(`/email/send?messageId=${messageId}`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update draft
|
|
||||||
export const updateDraft = async (messageId: number, draftData: unknown) => {
|
export const updateDraft = async (messageId: number, draftData: unknown) => {
|
||||||
const { data } = await axios.patch(
|
const { data } = await axios.patch(
|
||||||
`/email/messages/drafts/${messageId}`,
|
`/email/messages/drafts/${messageId}`,
|
||||||
@@ -77,7 +69,6 @@ export const updateDraft = async (messageId: number, draftData: unknown) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Delete draft
|
|
||||||
export const deleteDraft = async (messageId: number, mailbox: string) => {
|
export const deleteDraft = async (messageId: number, mailbox: string) => {
|
||||||
const { data } = await axios.delete(
|
const { data } = await axios.delete(
|
||||||
`/email/messages/drafts/${messageId}?mailbox=${mailbox}`
|
`/email/messages/drafts/${messageId}?mailbox=${mailbox}`
|
||||||
@@ -85,13 +76,11 @@ export const deleteDraft = async (messageId: number, mailbox: string) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Send draft
|
|
||||||
export const sendDraft = async (messageId: number) => {
|
export const sendDraft = async (messageId: number) => {
|
||||||
const { data } = await axios.post(`/email/messages/drafts/${messageId}/send`);
|
const { data } = await axios.post(`/email/messages/drafts/${messageId}/send`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unfavorite message
|
|
||||||
export const unfavoriteMessage = async (messageId: number, mailbox: string) => {
|
export const unfavoriteMessage = async (messageId: number, mailbox: string) => {
|
||||||
const { data } = await axios.patch(
|
const { data } = await axios.patch(
|
||||||
`/email/messages/${messageId}/unfavorite?mailbox=${mailbox}`
|
`/email/messages/${messageId}/unfavorite?mailbox=${mailbox}`
|
||||||
@@ -99,7 +88,6 @@ export const unfavoriteMessage = async (messageId: number, mailbox: string) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unarchive message
|
|
||||||
export const unarchiveMessage = async (messageId: number, mailbox: string) => {
|
export const unarchiveMessage = async (messageId: number, mailbox: string) => {
|
||||||
const { data } = await axios.patch(
|
const { data } = await axios.patch(
|
||||||
`/email/messages/${messageId}/unarchive?mailbox=${mailbox}`
|
`/email/messages/${messageId}/unarchive?mailbox=${mailbox}`
|
||||||
@@ -107,7 +95,6 @@ export const unarchiveMessage = async (messageId: number, mailbox: string) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Restore message from trash
|
|
||||||
export const restoreMessage = async (messageId: number, mailbox: string) => {
|
export const restoreMessage = async (messageId: number, mailbox: string) => {
|
||||||
const { data } = await axios.patch(
|
const { data } = await axios.patch(
|
||||||
`/email/messages/${messageId}/restore?mailbox=${mailbox}`
|
`/email/messages/${messageId}/restore?mailbox=${mailbox}`
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ const SideBar: FC = () => {
|
|||||||
|
|
||||||
|
|
||||||
const iconSizeSideBar = 20
|
const iconSizeSideBar = 20
|
||||||
// if (!mailboxCount?.data) return null
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user