From f67336eebee2c0c1b13b9cc7f13a376538d1fb35 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Mon, 28 Jul 2025 12:43:37 +0330 Subject: [PATCH] hold action mobile + najva token updated --- src/components/NajvaToken.tsx | 19 +--- src/components/Table.tsx | 111 +++++++++++++++----- src/pages/profile/hooks/useProfileData.tsx | 6 ++ src/pages/profile/service/ProfileService.ts | 5 + src/pages/received/List.tsx | 33 +++--- 5 files changed, 122 insertions(+), 52 deletions(-) diff --git a/src/components/NajvaToken.tsx b/src/components/NajvaToken.tsx index 2508a5a..b6e0622 100644 --- a/src/components/NajvaToken.tsx +++ b/src/components/NajvaToken.tsx @@ -1,5 +1,5 @@ +import { useUpdatePushToken } from '@/pages/profile/hooks/useProfileData' import { FC, useEffect, useState } from 'react' -import { updateProfile } from '../pages/profile/service/ProfileService' import { useLocation } from 'react-router-dom' declare global { @@ -15,36 +15,28 @@ const NajvaToken: FC = () => { const { pathname } = useLocation() const [isNajvaToken, setIsNajvaToken] = useState(false) const [retryCount, setRetryCount] = useState(0) + const updatePushToken = useUpdatePushToken() const handleNajvaToken = async () => { try { - console.log('تلاش برای دریافت توکن Najva...', { retryCount }) - // بررسی وجود window.NAJVA if (!window.NAJVA) { - console.log('window.NAJVA موجود نیست') return false } - console.log('window.NAJVA موجود است، در حال دریافت توکن...') const userToken = await window.NAJVA.getUserToken() - console.log('توکن دریافت شده:', userToken ? 'موجود' : 'خالی') - if (userToken) { - console.log('در حال ارسال توکن به سرور...') - await updateProfile({ + await updatePushToken.mutateAsync({ pushToken: userToken, }) setIsNajvaToken(true) - console.log('توکن با موفقیت ارسال شد') return true } else { - console.log('توکن خالی است') return false } } catch (error) { - console.error('خطا در دریافت توکن Najva:', error) + console.log(error); return false } } @@ -53,10 +45,9 @@ const NajvaToken: FC = () => { const success = await handleNajvaToken() if (!success && retryCount < 5) { - console.log(`تلاش مجدد در ${(retryCount + 1) * 1000}ms...`) setTimeout(() => { setRetryCount(prev => prev + 1) - }, (retryCount + 1) * 1000) // 1s, 2s, 3s, 4s, 5s + }, (retryCount + 1) * 1000) } } diff --git a/src/components/Table.tsx b/src/components/Table.tsx index 19cebca..cc80173 100644 --- a/src/components/Table.tsx +++ b/src/components/Table.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, useState } from 'react'; +import React, { Fragment, useState, useRef } from 'react'; import DefaultTableSkeleton from './DefaultTableSkeleton'; import Td from './Td'; import { TableProps, RowDataType, ColumnType } from './types/TableTypes'; @@ -31,6 +31,9 @@ const Table = ({ }: TableProps): React.ReactElement => { const [internalSelectedRows, setInternalSelectedRows] = useState([]); + const [mobileSelectionMode, setMobileSelectionMode] = useState(false); + const longPressTimer = useRef(null); + const [longPressActiveId, setLongPressActiveId] = useState(null); // استفاده از selectedRows خارجی اگر موجود باشد، در غیر این صورت از internal استفاده کن const selectedRows = externalSelectedRows !== undefined ? externalSelectedRows : internalSelectedRows; @@ -40,8 +43,59 @@ const Table = ({ if (clearSelection && externalSelectedRows === undefined) { setInternalSelectedRows([]); } + // خروج از حالت انتخاب موبایل هم + if (clearSelection) { + setMobileSelectionMode(false); + } }, [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 => { if (typeof rowClassName === 'function') { return rowClassName(item, index); @@ -136,36 +190,24 @@ const Table = ({ return (
handleRowClick(item)} + 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={() => handleMobileRowClick(item)} + onTouchStart={() => handleLongPressStart(item)} + onTouchEnd={handleLongPressEnd} + onTouchCancel={handleLongPressEnd} + onMouseDown={() => handleLongPressStart(item)} + onMouseUp={handleLongPressEnd} + onMouseLeave={handleLongPressEnd} > - {selectable && ( + {selectable && mobileSelectionMode && (
e.stopPropagation()} > handleSingleRowSelect(item, !!checked)} /> - - {/* {inlineActions && ( -
e.stopPropagation()} - > - {inlineActions(item).map((action, actionIndex) => ( -
- {action.icon} -
- ))} -
- )} */}
)} @@ -341,11 +383,32 @@ const Table = ({ {/* نسخه موبایل - Gmail style */}
{(actionsPosition === 'top' || actionsPosition === 'above-header') && ( -
+
{actions} {selectable && selectedRows.length > 0 && selectedActions && selectedActions}
+ {mobileSelectionMode && ( + + )}
)} {renderGmailStyleRows()} diff --git a/src/pages/profile/hooks/useProfileData.tsx b/src/pages/profile/hooks/useProfileData.tsx index c9f5164..bdee22e 100644 --- a/src/pages/profile/hooks/useProfileData.tsx +++ b/src/pages/profile/hooks/useProfileData.tsx @@ -43,4 +43,10 @@ export const useSingleUpload = () => { return useMutation({ mutationFn: (variables: FormData) => api.singleUpload(variables), }); +}; + +export const useUpdatePushToken = () => { + return useMutation({ + mutationFn: (variables: UpdateProfileType) => api.updatePushToken(variables), + }); }; \ No newline at end of file diff --git a/src/pages/profile/service/ProfileService.ts b/src/pages/profile/service/ProfileService.ts index dc33ec5..cf4b730 100644 --- a/src/pages/profile/service/ProfileService.ts +++ b/src/pages/profile/service/ProfileService.ts @@ -22,6 +22,11 @@ export const updateProfile = async (params: UpdateProfileType) => { 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) => { const { data } = await axios.patch(`/users/change-email`, params); return data; diff --git a/src/pages/received/List.tsx b/src/pages/received/List.tsx index bfa0e38..f440728 100644 --- a/src/pages/received/List.tsx +++ b/src/pages/received/List.tsx @@ -3,7 +3,7 @@ import { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import Table from '../../components/Table'; 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 { useNavigate } from 'react-router-dom'; import { useGetInbox, useMarkAllRead, useSummarizeEmail } from './hooks/useEmailData'; @@ -86,12 +86,6 @@ const List: FC = () => { className={`cursor-pointer min-w-[18px] ${isFetching ? 'animate-spin-reverse' : ''}`} onClick={() => refetch()} /> -
); @@ -103,23 +97,21 @@ const List: FC = () => { onClick={() => handleMarkAsReadSelected()} /> handleArchiveSelected()} - className="cursor-pointer" + className="cursor-pointer lg:size-[18px] size-[20px]" + /> - + handleFavoriteSelected()} /> handleDeleteSelected()} - className="cursor-pointer" + className="cursor-pointer lg:size-[18px] size-[20px]" /> ); @@ -236,6 +228,19 @@ const List: FC = () => { searchField="search" /> +
+ +
+ columns={columns} data={messages}