From ce12e99f6d76ba5a98f8b3f72d0ebb0c2b6861ca Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Mon, 17 Mar 2025 23:41:03 +0000 Subject: [PATCH] Fix transaction deletion issue Addresses an issue where the application becomes unresponsive after deleting a transaction. --- .../deleteOperation/deleteTransactionCore.ts | 11 ++-- .../deleteTransactionStorage.ts | 65 ++++++++++++------- .../transactions/useTransactionsEvents.ts | 47 ++++++++++++-- src/pages/Transactions.tsx | 32 ++++++--- 4 files changed, 116 insertions(+), 39 deletions(-) diff --git a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts index 1bbe806..8c39059 100644 --- a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts +++ b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts @@ -21,9 +21,12 @@ export const useDeleteTransactionCore = ( pendingDeletionRef.current = new Set(); } + // 프로미스 객체와 취소 함수 참조를 위한 변수 선언 + let promiseObj: Promise & { cancel?: () => void }; + // 기존 promise를 변수로 저장해서 참조 가능하게 함 - const promiseObj = new Promise((resolve, reject) => { - // 삭제 작업 취소 플래그 초기화 + promiseObj = new Promise((resolve, reject) => { + // 삭제 작업 취소 플래그 초기화 - 프로미스 내부에서 선언 let isCanceled = false; try { @@ -106,8 +109,8 @@ export const useDeleteTransactionCore = ( reject(error); } - // cancel 함수에서 참조할 수 있도록 클로저로 isCanceled 변수 노출 - (promiseObj as any).cancel = () => { + // cancel 함수를 프로미스 객체에 연결 (프로미스 내부에서) + promiseObj.cancel = () => { isCanceled = true; pendingDeletionRef.current?.delete(id); console.log('트랜잭션 삭제 작업 취소 완료'); diff --git a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts index f418583..e299e10 100644 --- a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts +++ b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts @@ -24,30 +24,49 @@ export const handleDeleteStorage = ( // 로컬 스토리지 업데이트 saveTransactionsToStorage(updatedTransactions); + console.log('로컬 스토리지 저장 완료'); - // Supabase 업데이트 + // Supabase 업데이트 (비동기 처리) if (user) { - deleteTransactionFromSupabase(user, id) - .catch(error => { - console.error('Supabase 삭제 오류:', error); - // 오류 시에도 UI에는 영향이 없도록 함 (이미 로컬에서는 제거됨) - }) - .finally(() => { - // 삭제 완료 후 토스트 표시 - if (!isCanceled) { - toast({ - title: "삭제 완료", - description: "지출 항목이 삭제되었습니다.", - duration: 2000 + // 동기적 에러를 피하기 위해 setTimeout으로 감싸기 + setTimeout(() => { + try { + deleteTransactionFromSupabase(user, id) + .catch(error => { + console.error('Supabase 삭제 오류:', error); + }) + .finally(() => { + // 삭제 완료 후 토스트 표시 (취소되지 않은 경우만) + if (!isCanceled) { + toast({ + title: "삭제 완료", + description: "지출 항목이 삭제되었습니다.", + duration: 2000 + }); + } }); - } + } catch (e) { + console.error('Supabase 작업 초기화 오류:', e); + } + }, 10); + } else { + // 사용자가 없는 경우 토스트 표시 + if (!isCanceled) { + toast({ + title: "삭제 완료", + description: "지출 항목이 삭제되었습니다.", + duration: 2000 }); + } } - // 삭제 완료 이벤트 발생 + // 삭제 완료 이벤트 발생 - 지연 처리로 안정성 향상 setTimeout(() => { try { - window.dispatchEvent(new Event('transactionDeleted')); + if (!isCanceled) { + console.log('트랜잭션 삭제 완료 이벤트 발생'); + window.dispatchEvent(new Event('transactionDeleted')); + } } catch (e) { console.error('이벤트 발생 오류:', e); } @@ -57,12 +76,14 @@ export const handleDeleteStorage = ( console.error('스토리지 작업 중 오류:', storageError); // 삭제 실패 알림 - toast({ - title: "삭제 중 오류", - description: "지출 항목 삭제 중 문제가 발생했습니다.", - variant: "destructive", - duration: 2000 - }); + if (!isCanceled) { + toast({ + title: "삭제 중 오류", + description: "지출 항목 삭제 중 문제가 발생했습니다.", + variant: "destructive", + duration: 2000 + }); + } } }; diff --git a/src/hooks/transactions/useTransactionsEvents.ts b/src/hooks/transactions/useTransactionsEvents.ts index b4539ef..422533d 100644 --- a/src/hooks/transactions/useTransactionsEvents.ts +++ b/src/hooks/transactions/useTransactionsEvents.ts @@ -12,30 +12,65 @@ export const useTransactionsEvents = ( useEffect(() => { console.log('useTransactions - 이벤트 리스너 설정'); + // 바운싱 방지 변수 + let isProcessing = false; + // 트랜잭션 업데이트 이벤트 const handleTransactionUpdate = (e?: any) => { console.log('트랜잭션 업데이트 이벤트 감지:', e); - loadTransactions(); + + // 처리 중 중복 호출 방지 + if (isProcessing) return; + + isProcessing = true; + setTimeout(() => { + loadTransactions(); + isProcessing = false; + }, 150); }; // 트랜잭션 삭제 이벤트 const handleTransactionDelete = () => { console.log('트랜잭션 삭제 이벤트 감지됨'); - loadTransactions(); + + // 처리 중 중복 호출 방지 + if (isProcessing) return; + + isProcessing = true; + setTimeout(() => { + loadTransactions(); + isProcessing = false; + }, 200); }; // 스토리지 이벤트 const handleStorageEvent = (e: StorageEvent) => { if (e.key === 'transactions' || e.key === null) { console.log('스토리지 이벤트 감지:', e.key); - loadTransactions(); + + // 처리 중 중복 호출 방지 + if (isProcessing) return; + + isProcessing = true; + setTimeout(() => { + loadTransactions(); + isProcessing = false; + }, 150); } }; // 포커스 이벤트 const handleFocus = () => { console.log('창 포커스: 트랜잭션 새로고침'); - loadTransactions(); + + // 처리 중 중복 호출 방지 + if (isProcessing) return; + + isProcessing = true; + setTimeout(() => { + loadTransactions(); + isProcessing = false; + }, 200); }; // 이벤트 리스너 등록 @@ -45,7 +80,9 @@ export const useTransactionsEvents = ( window.addEventListener('focus', handleFocus); // 새로고침 키가 변경되면 데이터 로드 - loadTransactions(); + if (!isProcessing) { + loadTransactions(); + } // 클린업 함수 return () => { diff --git a/src/pages/Transactions.tsx b/src/pages/Transactions.tsx index 1c61d72..1c9a721 100644 --- a/src/pages/Transactions.tsx +++ b/src/pages/Transactions.tsx @@ -25,6 +25,8 @@ const Transactions = () => { const { budgetData } = useBudget(); const [isDataLoaded, setIsDataLoaded] = useState(false); const [isProcessing, setIsProcessing] = useState(false); + // 트랜잭션 삭제 중인 ID 추적 + const [deletingId, setDeletingId] = useState(null); // 데이터 로드 상태 관리 useEffect(() => { @@ -33,23 +35,32 @@ const Transactions = () => { } }, [budgetData, isLoading]); - // 트랜잭션 삭제 핸들러 (오류 수정) + // 트랜잭션 삭제 핸들러 (안정성 개선) const handleTransactionDelete = async (id: string) => { + // 이미 처리 중인 삭제 작업이 있다면 취소 + if (isProcessing || deletingId) { + console.log('이미 삭제 작업이 진행 중입니다:', deletingId); + return; + } + try { console.log('Transactions 페이지에서 트랜잭션 삭제:', id); // 삭제 중임을 표시 setIsProcessing(true); + setDeletingId(id); - // 트랜잭션 삭제 수행 (개선된 함수 사용) + // 트랜잭션 삭제 수행 const success = await deleteTransaction(id); // 일정 시간 후 처리 상태 해제 (UI 응답성 향상) setTimeout(() => { setIsProcessing(false); + setDeletingId(null); // 삭제 성공 시 데이터 새로고침 if (success) { + console.log('삭제 성공, 데이터 새로고침'); setTimeout(() => { refreshTransactions(); }, 300); @@ -57,9 +68,12 @@ const Transactions = () => { }, 800); } catch (error) { console.error('트랜잭션 삭제 처리 중 오류:', error); - setIsProcessing(false); - // 삭제 실패 시에도 데이터 새로고침 (안정성 향상) + // 오류 발생 시 상태 초기화 + setIsProcessing(false); + setDeletingId(null); + + // 오류 후에도 데이터 새로고침 (안정성 향상) setTimeout(() => { refreshTransactions(); }, 500); @@ -69,15 +83,17 @@ const Transactions = () => { // 페이지 포커스나 가시성 변경 시 데이터 새로고침 useEffect(() => { const handleVisibilityChange = () => { - if (document.visibilityState === 'visible') { + if (document.visibilityState === 'visible' && !isProcessing) { console.log('거래내역 페이지 보임 - 데이터 새로고침'); refreshTransactions(); } }; const handleFocus = () => { - console.log('거래내역 페이지 포커스 - 데이터 새로고침'); - refreshTransactions(); + if (!isProcessing) { + console.log('거래내역 페이지 포커스 - 데이터 새로고침'); + refreshTransactions(); + } }; document.addEventListener('visibilitychange', handleVisibilityChange); @@ -87,7 +103,7 @@ const Transactions = () => { document.removeEventListener('visibilitychange', handleVisibilityChange); window.removeEventListener('focus', handleFocus); }; - }, [refreshTransactions]); + }, [refreshTransactions, isProcessing]); // 트랜잭션을 날짜별로 그룹화 const groupTransactionsByDate = (transactions: Transaction[]): Record => {