hold action mobile + najva token updated
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
|
import { useUpdatePushToken } from '@/pages/profile/hooks/useProfileData'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { updateProfile } from '../pages/profile/service/ProfileService'
|
|
||||||
import { useLocation } from 'react-router-dom'
|
import { useLocation } from 'react-router-dom'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -15,36 +15,28 @@ const NajvaToken: FC = () => {
|
|||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const [isNajvaToken, setIsNajvaToken] = useState(false)
|
const [isNajvaToken, setIsNajvaToken] = useState(false)
|
||||||
const [retryCount, setRetryCount] = useState(0)
|
const [retryCount, setRetryCount] = useState(0)
|
||||||
|
const updatePushToken = useUpdatePushToken()
|
||||||
|
|
||||||
const handleNajvaToken = async () => {
|
const handleNajvaToken = async () => {
|
||||||
try {
|
try {
|
||||||
console.log('تلاش برای دریافت توکن Najva...', { retryCount })
|
|
||||||
|
|
||||||
// بررسی وجود window.NAJVA
|
|
||||||
if (!window.NAJVA) {
|
if (!window.NAJVA) {
|
||||||
console.log('window.NAJVA موجود نیست')
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('window.NAJVA موجود است، در حال دریافت توکن...')
|
|
||||||
const userToken = await window.NAJVA.getUserToken()
|
const userToken = await window.NAJVA.getUserToken()
|
||||||
|
|
||||||
console.log('توکن دریافت شده:', userToken ? 'موجود' : 'خالی')
|
|
||||||
|
|
||||||
if (userToken) {
|
if (userToken) {
|
||||||
console.log('در حال ارسال توکن به سرور...')
|
await updatePushToken.mutateAsync({
|
||||||
await updateProfile({
|
|
||||||
pushToken: userToken,
|
pushToken: userToken,
|
||||||
})
|
})
|
||||||
setIsNajvaToken(true)
|
setIsNajvaToken(true)
|
||||||
console.log('توکن با موفقیت ارسال شد')
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
console.log('توکن خالی است')
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('خطا در دریافت توکن Najva:', error)
|
console.log(error);
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,10 +45,9 @@ const NajvaToken: FC = () => {
|
|||||||
const success = await handleNajvaToken()
|
const success = await handleNajvaToken()
|
||||||
|
|
||||||
if (!success && retryCount < 5) {
|
if (!success && retryCount < 5) {
|
||||||
console.log(`تلاش مجدد در ${(retryCount + 1) * 1000}ms...`)
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setRetryCount(prev => prev + 1)
|
setRetryCount(prev => prev + 1)
|
||||||
}, (retryCount + 1) * 1000) // 1s, 2s, 3s, 4s, 5s
|
}, (retryCount + 1) * 1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+87
-24
@@ -1,4 +1,4 @@
|
|||||||
import React, { Fragment, useState } from 'react';
|
import React, { Fragment, useState, useRef } from 'react';
|
||||||
import DefaultTableSkeleton from './DefaultTableSkeleton';
|
import DefaultTableSkeleton from './DefaultTableSkeleton';
|
||||||
import Td from './Td';
|
import Td from './Td';
|
||||||
import { TableProps, RowDataType, ColumnType } from './types/TableTypes';
|
import { TableProps, RowDataType, ColumnType } from './types/TableTypes';
|
||||||
@@ -31,6 +31,9 @@ const Table = <T extends RowDataType>({
|
|||||||
}: TableProps<T>): React.ReactElement => {
|
}: TableProps<T>): React.ReactElement => {
|
||||||
|
|
||||||
const [internalSelectedRows, setInternalSelectedRows] = useState<T[]>([]);
|
const [internalSelectedRows, setInternalSelectedRows] = useState<T[]>([]);
|
||||||
|
const [mobileSelectionMode, setMobileSelectionMode] = useState(false);
|
||||||
|
const longPressTimer = useRef<number | null>(null);
|
||||||
|
const [longPressActiveId, setLongPressActiveId] = useState<string | number | null>(null);
|
||||||
|
|
||||||
// استفاده از selectedRows خارجی اگر موجود باشد، در غیر این صورت از internal استفاده کن
|
// استفاده از selectedRows خارجی اگر موجود باشد، در غیر این صورت از internal استفاده کن
|
||||||
const selectedRows = externalSelectedRows !== undefined ? externalSelectedRows : internalSelectedRows;
|
const selectedRows = externalSelectedRows !== undefined ? externalSelectedRows : internalSelectedRows;
|
||||||
@@ -40,8 +43,59 @@ const Table = <T extends RowDataType>({
|
|||||||
if (clearSelection && externalSelectedRows === undefined) {
|
if (clearSelection && externalSelectedRows === undefined) {
|
||||||
setInternalSelectedRows([]);
|
setInternalSelectedRows([]);
|
||||||
}
|
}
|
||||||
|
// خروج از حالت انتخاب موبایل هم
|
||||||
|
if (clearSelection) {
|
||||||
|
setMobileSelectionMode(false);
|
||||||
|
}
|
||||||
}, [clearSelection, externalSelectedRows]);
|
}, [clearSelection, externalSelectedRows]);
|
||||||
|
|
||||||
|
// تمیز کردن timer هنگام unmount
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (longPressTimer.current) {
|
||||||
|
clearTimeout(longPressTimer.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// خروج از حالت انتخاب اگر هیچ آیتمی انتخاب نشده
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (selectedRows.length === 0 && mobileSelectionMode) {
|
||||||
|
setMobileSelectionMode(false);
|
||||||
|
}
|
||||||
|
}, [selectedRows.length, mobileSelectionMode]);
|
||||||
|
|
||||||
|
const handleLongPressStart = (item: T) => {
|
||||||
|
if (!selectable || mobileSelectionMode) return;
|
||||||
|
|
||||||
|
setLongPressActiveId(item.id);
|
||||||
|
longPressTimer.current = window.setTimeout(() => {
|
||||||
|
setMobileSelectionMode(true);
|
||||||
|
// انتخاب آیتم فعلی
|
||||||
|
handleSingleRowSelect(item, true);
|
||||||
|
setLongPressActiveId(null);
|
||||||
|
}, 500); // 500ms برای long press
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLongPressEnd = () => {
|
||||||
|
if (longPressTimer.current) {
|
||||||
|
clearTimeout(longPressTimer.current);
|
||||||
|
longPressTimer.current = null;
|
||||||
|
}
|
||||||
|
setLongPressActiveId(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMobileRowClick = (item: T) => {
|
||||||
|
if (mobileSelectionMode) {
|
||||||
|
// در حالت انتخاب، آیتم را انتخاب/عدم انتخاب کن
|
||||||
|
const isSelected = isRowSelected(item);
|
||||||
|
handleSingleRowSelect(item, !isSelected);
|
||||||
|
} else if (longPressActiveId !== item.id) {
|
||||||
|
// در حالت عادی، row click عادی را اجرا کن
|
||||||
|
handleRowClick(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getRowClassName = (item: T, index: number): string => {
|
const getRowClassName = (item: T, index: number): string => {
|
||||||
if (typeof rowClassName === 'function') {
|
if (typeof rowClassName === 'function') {
|
||||||
return rowClassName(item, index);
|
return rowClassName(item, index);
|
||||||
@@ -136,36 +190,24 @@ const Table = <T extends RowDataType>({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={item.id}
|
key={item.id}
|
||||||
className={`w-full min-h-[85px] ${bgColor} ${hoverColor} ${onRowClick ? 'cursor-pointer' : ''} ${getRowClassName(item, rowIndex)} px-4 py-3 flex items-center gap-3 ${rowIndex !== 0 ? 'border-t border-[#EAEDF5]' : ''}`}
|
className={`w-full min-h-[85px] ${bgColor} ${hoverColor} ${mobileSelectionMode || !onRowClick ? '' : 'cursor-pointer'} ${getRowClassName(item, rowIndex)} px-4 py-3 flex items-center gap-3 ${rowIndex !== 0 ? 'border-t border-[#EAEDF5]' : ''} ${longPressActiveId === item.id ? 'bg-blue-50' : ''}`}
|
||||||
onClick={() => handleRowClick(item)}
|
onClick={() => handleMobileRowClick(item)}
|
||||||
|
onTouchStart={() => handleLongPressStart(item)}
|
||||||
|
onTouchEnd={handleLongPressEnd}
|
||||||
|
onTouchCancel={handleLongPressEnd}
|
||||||
|
onMouseDown={() => handleLongPressStart(item)}
|
||||||
|
onMouseUp={handleLongPressEnd}
|
||||||
|
onMouseLeave={handleLongPressEnd}
|
||||||
>
|
>
|
||||||
{selectable && (
|
{selectable && mobileSelectionMode && (
|
||||||
<div
|
<div
|
||||||
className="flex-shrink-0 "
|
className="flex-shrink-0"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={isRowSelected(item)}
|
checked={isRowSelected(item)}
|
||||||
onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)}
|
onCheckedChange={(checked) => handleSingleRowSelect(item, !!checked)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* {inlineActions && (
|
|
||||||
<div
|
|
||||||
className="flex-shrink-0 flex items-center gap-2 "
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{inlineActions(item).map((action, actionIndex) => (
|
|
||||||
<div
|
|
||||||
key={actionIndex}
|
|
||||||
className={`cursor-pointer ${action.className || ''}`}
|
|
||||||
onClick={action.onClick}
|
|
||||||
title={action.tooltip}
|
|
||||||
>
|
|
||||||
{action.icon}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)} */}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -341,11 +383,32 @@ const Table = <T extends RowDataType>({
|
|||||||
{/* نسخه موبایل - Gmail style */}
|
{/* نسخه موبایل - 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 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]">
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
{actions}
|
{actions}
|
||||||
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
|
{selectable && selectedRows.length > 0 && selectedActions && selectedActions}
|
||||||
</div>
|
</div>
|
||||||
|
{mobileSelectionMode && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setMobileSelectionMode(false);
|
||||||
|
// پاک کردن انتخابها
|
||||||
|
if (externalSelectedRows !== undefined) {
|
||||||
|
if (onSelectionChange) {
|
||||||
|
onSelectionChange([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setInternalSelectedRows([]);
|
||||||
|
if (onSelectionChange) {
|
||||||
|
onSelectionChange([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="text-sm text-gray-600 px-3 py-1"
|
||||||
|
>
|
||||||
|
انصراف
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{renderGmailStyleRows()}
|
{renderGmailStyleRows()}
|
||||||
|
|||||||
@@ -44,3 +44,9 @@ export const useSingleUpload = () => {
|
|||||||
mutationFn: (variables: FormData) => api.singleUpload(variables),
|
mutationFn: (variables: FormData) => api.singleUpload(variables),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUpdatePushToken = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: UpdateProfileType) => api.updatePushToken(variables),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -22,6 +22,11 @@ export const updateProfile = async (params: UpdateProfileType) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updatePushToken = async (params: UpdateProfileType) => {
|
||||||
|
const { data } = await axios.post(`/users/push-token`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
export const updateEmail = async (params: UpdateEmailType) => {
|
export const updateEmail = async (params: UpdateEmailType) => {
|
||||||
const { data } = await axios.patch(`/users/change-email`, params);
|
const { data } = await axios.patch(`/users/change-email`, params);
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
+19
-14
@@ -3,7 +3,7 @@ import { FC, useState } from 'react'
|
|||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import Table from '../../components/Table';
|
import Table from '../../components/Table';
|
||||||
import { ColumnType } from '../../components/types/TableTypes';
|
import { ColumnType } from '../../components/types/TableTypes';
|
||||||
import { InfoCircle, Refresh2, Trash, ArchiveTick, Star1 } from 'iconsax-react';
|
import { InfoCircle, Refresh2, Trash, ArchiveTick, Star1, Sms } from 'iconsax-react';
|
||||||
import { RowActionItem } from '../../components/RowActionsDropdown';
|
import { RowActionItem } from '../../components/RowActionsDropdown';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useGetInbox, useMarkAllRead, useSummarizeEmail } from './hooks/useEmailData';
|
import { useGetInbox, useMarkAllRead, useSummarizeEmail } from './hooks/useEmailData';
|
||||||
@@ -86,12 +86,6 @@ const List: FC = () => {
|
|||||||
className={`cursor-pointer min-w-[18px] ${isFetching ? 'animate-spin-reverse' : ''}`}
|
className={`cursor-pointer min-w-[18px] ${isFetching ? 'animate-spin-reverse' : ''}`}
|
||||||
onClick={() => refetch()}
|
onClick={() => refetch()}
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
label='علامتگذاری همه به عنوان خوانده شده'
|
|
||||||
className='h-8'
|
|
||||||
onClick={() => setShowModal(true)}
|
|
||||||
loading={isMarkingAllRead}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,23 +97,21 @@ const List: FC = () => {
|
|||||||
onClick={() => handleMarkAsReadSelected()}
|
onClick={() => handleMarkAsReadSelected()}
|
||||||
/>
|
/>
|
||||||
<ArchiveTick
|
<ArchiveTick
|
||||||
size={18}
|
|
||||||
color='black'
|
color='black'
|
||||||
onClick={() => handleArchiveSelected()}
|
onClick={() => handleArchiveSelected()}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer lg:size-[18px] size-[20px]"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
<InfoCircle size={18} color='black' className="cursor-pointer" />
|
<InfoCircle color='black' className="cursor-pointer lg:size-[18px] size-[20px]" />
|
||||||
<Star1
|
<Star1
|
||||||
size={18}
|
|
||||||
color='black'
|
color='black'
|
||||||
className="cursor-pointer"
|
className="cursor-pointer lg:size-[18px] size-[20px]"
|
||||||
onClick={() => handleFavoriteSelected()}
|
onClick={() => handleFavoriteSelected()}
|
||||||
/>
|
/>
|
||||||
<Trash
|
<Trash
|
||||||
size={18}
|
|
||||||
color='#CD0000'
|
color='#CD0000'
|
||||||
onClick={() => handleDeleteSelected()}
|
onClick={() => handleDeleteSelected()}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer lg:size-[18px] size-[20px]"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -236,6 +228,19 @@ const List: FC = () => {
|
|||||||
searchField="search"
|
searchField="search"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className='flex justify-end mt-5 lg:mt-10 '>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowModal(true)}
|
||||||
|
loading={isMarkingAllRead}
|
||||||
|
className='w-fit'
|
||||||
|
>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<Sms size={18} color='white' />
|
||||||
|
<span>خواندن همه</span>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Table<InboxMessage>
|
<Table<InboxMessage>
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={messages}
|
data={messages}
|
||||||
|
|||||||
Reference in New Issue
Block a user