diff --git a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts index 2aed7d8..f9fd6fd 100644 --- a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts +++ b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts @@ -25,7 +25,6 @@ export const useDeleteTransactionCore = ( const promiseObj = new Promise((resolve, reject) => { // 삭제 작업 취소 플래그 초기화 let isCanceled = false; - let timeoutId: ReturnType | null = null; try { console.log('트랜잭션 삭제 작업 시작 - ID:', id); @@ -33,16 +32,14 @@ export const useDeleteTransactionCore = ( // 이미 삭제 중인 트랜잭션인지 확인 if (pendingDeletionRef.current.has(id)) { console.warn('이미 삭제 중인 트랜잭션입니다:', id); - reject(new Error('이미 삭제 중인 트랜잭션입니다')); - return; + return resolve(false); } // 삭제할 트랜잭션이 존재하는지 확인 및 데이터 복사 보관 const transactionToDelete = transactions.find(t => t.id === id); if (!transactionToDelete) { console.warn('삭제할 트랜잭션이 존재하지 않음:', id); - reject(new Error('트랜잭션이 존재하지 않습니다')); - return; + return resolve(false); } // 삭제 중인 상태로 표시 @@ -56,7 +53,7 @@ export const useDeleteTransactionCore = ( // 사용자 인터페이스 응답성 감소 전에 이벤트 발생 처리 try { - // 상태 업데이트 바로 후 크로스 스레드 통신 방지 + // 상태 업데이트 바로 후 이벤트 발생 setTimeout(() => { try { window.dispatchEvent(new Event('transactionUpdated')); @@ -68,48 +65,37 @@ export const useDeleteTransactionCore = ( console.warn('이벤트 디스패치 설정 오류:', eventError); } - // UI 스레드 블록하지 않는 너비로 requestAnimationFrame 사용 - requestAnimationFrame(() => { + // 백그라운드 작업 처리 + setTimeout(() => { if (isCanceled) { console.log('작업이 취소되었습니다.'); return; } - // 백그라운드 작업은 너비로 처리 - timeoutId = setTimeout(() => { - handleDeleteStorage( - isCanceled, - updatedTransactions, - id, - user, - transactionToDelete, - pendingDeletionRef - ); - }, 0); - }); + // 스토리지 처리 + handleDeleteStorage( + isCanceled, + updatedTransactions, + id, + user, + transactionToDelete, + pendingDeletionRef + ); + + // 작업 완료 후 보류 중인 삭제 목록에서 제거 + pendingDeletionRef.current?.delete(id); + }, 50); // 상태 업데이트가 이미 수행되었으므로 즉시 성공 반환 console.log('트랜잭션 삭제 UI 업데이트 완료'); resolve(true); - // 취소 기능을 가진 Promise 객체 생성 - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (promiseObj as any).cancel = () => { - isCanceled = true; - - if (timeoutId !== null) { - clearTimeout(timeoutId); - } - - pendingDeletionRef.current?.delete(id); - console.log('트랜잭션 삭제 작업 취소 완료'); - }; } catch (error) { console.error('트랜잭션 삭제 초기화 중 오류:', error); // 오류 발생 시 토스트 표시 toast({ - title: "시스템 오류", + title: "삭제 실패", description: "지출 삭제 중 오류가 발생했습니다.", duration: 2000, variant: "destructive" @@ -121,6 +107,13 @@ export const useDeleteTransactionCore = ( } }); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (promiseObj as any).cancel = () => { + isCanceled = true; + pendingDeletionRef.current?.delete(id); + console.log('트랜잭션 삭제 작업 취소 완료'); + }; + return promiseObj; }, [transactions, setTransactions, user]); }; diff --git a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts index 6eb737f..f418583 100644 --- a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts +++ b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts @@ -3,6 +3,7 @@ import { MutableRefObject } from 'react'; import { Transaction } from '@/components/TransactionCard'; import { saveTransactionsToStorage } from '../../storageUtils'; import { deleteTransactionFromSupabase } from '../../supabaseUtils'; +import { toast } from '@/hooks/useToast.wrapper'; /** * 스토리지 및 Supabase 삭제 처리 @@ -27,29 +28,41 @@ export const handleDeleteStorage = ( // Supabase 업데이트 if (user) { deleteTransactionFromSupabase(user, id) - .then(() => { - if (!isCanceled) { - console.log('Supabase 트랜잭션 삭제 성공'); - // 성공 로그만 추가, UI 업데이트는 이미 수행됨 - } - }) .catch(error => { console.error('Supabase 삭제 오류:', error); - handleDeleteError(error, isCanceled, id, transactionToDelete, pendingDeletionRef); + // 오류 시에도 UI에는 영향이 없도록 함 (이미 로컬에서는 제거됨) }) .finally(() => { + // 삭제 완료 후 토스트 표시 if (!isCanceled) { - // 작업 완료 후 보류 중인 삭제 목록에서 제거 - pendingDeletionRef.current?.delete(id); + toast({ + title: "삭제 완료", + description: "지출 항목이 삭제되었습니다.", + duration: 2000 + }); } }); - } else { - // 사용자 정보 없을 경우 목록에서 제거 - pendingDeletionRef.current?.delete(id); } + + // 삭제 완료 이벤트 발생 + setTimeout(() => { + try { + window.dispatchEvent(new Event('transactionDeleted')); + } catch (e) { + console.error('이벤트 발생 오류:', e); + } + }, 100); + } catch (storageError) { console.error('스토리지 작업 중 오류:', storageError); - pendingDeletionRef.current?.delete(id); + + // 삭제 실패 알림 + toast({ + title: "삭제 중 오류", + description: "지출 항목 삭제 중 문제가 발생했습니다.", + variant: "destructive", + duration: 2000 + }); } }; diff --git a/src/hooks/transactions/transactionOperations/deleteTransaction.ts b/src/hooks/transactions/transactionOperations/deleteTransaction.ts index 80e79ad..f8d1694 100644 --- a/src/hooks/transactions/transactionOperations/deleteTransaction.ts +++ b/src/hooks/transactions/transactionOperations/deleteTransaction.ts @@ -20,15 +20,20 @@ export const useDeleteTransaction = ( // 핵심 삭제 로직 사용 const deleteTransactionHandler = useDeleteTransactionCore(transactions, setTransactions, user, pendingDeletionRef); - // 디버깅 추가 - const deleteTransaction = useCallback((id: string) => { + // 디버깅 및 안정성 추가 + const deleteTransaction = useCallback(async (id: string) => { console.log('트랜잭션 삭제 시작:', id); try { // 이미 삭제 중인지 확인 if (pendingDeletionRef.current.has(id)) { console.log('이미 삭제 중인 트랜잭션:', id); - return; + toast({ + title: "처리 중", + description: "이전 삭제 작업이 진행 중입니다. 잠시 기다려주세요.", + duration: 1500 + }); + return false; } // 트랜잭션이 존재하는지 확인 @@ -37,21 +42,25 @@ export const useDeleteTransaction = ( console.error('존재하지 않는 트랜잭션 ID:', id); toast({ title: "삭제 실패", - description: "존재하지 않는 트랜잭션입니다.", - variant: "destructive" + description: "해당 지출 항목을 찾을 수 없습니다.", + variant: "destructive", + duration: 1500 }); - return; + return false; } - // 삭제 실행 - return deleteTransactionHandler(id); + // 삭제 실행 (비동기 처리) + const result = await deleteTransactionHandler(id); + return result; } catch (error) { console.error('트랜잭션 삭제 오류:', error); toast({ title: "삭제 실패", - description: "트랜잭션 삭제 중 오류가 발생했습니다.", - variant: "destructive" + description: "처리 중 오류가 발생했습니다. 다시 시도해주세요.", + variant: "destructive", + duration: 1500 }); + return false; } }, [transactions, deleteTransactionHandler]); diff --git a/src/hooks/transactions/useTransactionsCore.ts b/src/hooks/transactions/useTransactionsCore.ts index 2fc3966..2c22487 100644 --- a/src/hooks/transactions/useTransactionsCore.ts +++ b/src/hooks/transactions/useTransactionsCore.ts @@ -61,7 +61,7 @@ export const useTransactionsCore = () => { setTransactions ); - // 이벤트 리스너 + // 이벤트 리스너 - 삭제 이벤트 포함 useTransactionsEvents(loadTransactions, refreshKey); // 데이터 강제 새로고침 diff --git a/src/hooks/transactions/useTransactionsEvents.ts b/src/hooks/transactions/useTransactionsEvents.ts index 973eaa8..b4539ef 100644 --- a/src/hooks/transactions/useTransactionsEvents.ts +++ b/src/hooks/transactions/useTransactionsEvents.ts @@ -2,58 +2,58 @@ import { useEffect } from 'react'; /** - * 트랜잭션 이벤트 관련 훅 - * 각종 이벤트 리스너를 설정합니다. + * 트랜잭션 이벤트 리스너 훅 + * 트랜잭션 업데이트 이벤트를 리스닝합니다. */ export const useTransactionsEvents = ( loadTransactions: () => void, refreshKey: number ) => { - // 이벤트 리스너 설정 useEffect(() => { console.log('useTransactions - 이벤트 리스너 설정'); - // 트랜잭션 업데이트 이벤트 리스너 - const handleTransactionUpdated = () => { - console.log('트랜잭션 업데이트 이벤트 감지됨'); + // 트랜잭션 업데이트 이벤트 + const handleTransactionUpdate = (e?: any) => { + console.log('트랜잭션 업데이트 이벤트 감지:', e); loadTransactions(); }; - // 스토리지 변경 이벤트 리스너 - const handleStorageChange = (e: StorageEvent) => { + // 트랜잭션 삭제 이벤트 + const handleTransactionDelete = () => { + console.log('트랜잭션 삭제 이벤트 감지됨'); + loadTransactions(); + }; + + // 스토리지 이벤트 + const handleStorageEvent = (e: StorageEvent) => { if (e.key === 'transactions' || e.key === null) { - console.log('로컬 스토리지 변경 감지됨:', e.key); + console.log('스토리지 이벤트 감지:', e.key); loadTransactions(); } }; - // 페이지 포커스/가시성 이벤트 리스너 + // 포커스 이벤트 const handleFocus = () => { - console.log('창 포커스 - 트랜잭션 새로고침'); + console.log('창 포커스: 트랜잭션 새로고침'); loadTransactions(); }; - const handleVisibilityChange = () => { - if (document.visibilityState === 'visible') { - console.log('페이지 가시성 변경 - 트랜잭션 새로고침'); - loadTransactions(); - } - }; - // 이벤트 리스너 등록 - window.addEventListener('transactionUpdated', handleTransactionUpdated); - window.addEventListener('storage', handleStorageChange); + window.addEventListener('transactionUpdated', handleTransactionUpdate); + window.addEventListener('transactionDeleted', handleTransactionDelete); + window.addEventListener('storage', handleStorageEvent); window.addEventListener('focus', handleFocus); - document.addEventListener('visibilitychange', handleVisibilityChange); - // 컴포넌트 마운트시에만 수동으로 트랜잭션 업데이트 이벤트 발생 - window.dispatchEvent(new Event('transactionUpdated')); + // 새로고침 키가 변경되면 데이터 로드 + loadTransactions(); + // 클린업 함수 return () => { - window.removeEventListener('transactionUpdated', handleTransactionUpdated); - window.removeEventListener('storage', handleStorageChange); + console.log('useTransactions - 이벤트 리스너 제거'); + window.removeEventListener('transactionUpdated', handleTransactionUpdate); + window.removeEventListener('transactionDeleted', handleTransactionDelete); + window.removeEventListener('storage', handleStorageEvent); window.removeEventListener('focus', handleFocus); - document.removeEventListener('visibilitychange', handleVisibilityChange); }; }, [loadTransactions, refreshKey]); }; diff --git a/src/pages/Transactions.tsx b/src/pages/Transactions.tsx index 723c0b3..1c61d72 100644 --- a/src/pages/Transactions.tsx +++ b/src/pages/Transactions.tsx @@ -19,6 +19,7 @@ const Transactions = () => { handleNextMonth, refreshTransactions, totalExpenses, + deleteTransaction } = useTransactions(); const { budgetData } = useBudget(); @@ -32,25 +33,36 @@ const Transactions = () => { } }, [budgetData, isLoading]); - // 트랜잭션 삭제 핸들러 (예외 처리 개선) - const handleTransactionDelete = (id: string) => { + // 트랜잭션 삭제 핸들러 (오류 수정) + const handleTransactionDelete = async (id: string) => { try { console.log('Transactions 페이지에서 트랜잭션 삭제:', id); + // 삭제 중임을 표시 setIsProcessing(true); - // 1초 후 처리 완료 상태로 변경 (비동기 작업 완료 보장) + // 트랜잭션 삭제 수행 (개선된 함수 사용) + const success = await deleteTransaction(id); + + // 일정 시간 후 처리 상태 해제 (UI 응답성 향상) setTimeout(() => { setIsProcessing(false); - }, 1000); - - // 트랜잭션 목록 새로고침 (지연시켜 처리) - setTimeout(() => { - refreshTransactions(); - }, 300); + + // 삭제 성공 시 데이터 새로고침 + if (success) { + setTimeout(() => { + refreshTransactions(); + }, 300); + } + }, 800); } catch (error) { console.error('트랜잭션 삭제 처리 중 오류:', error); setIsProcessing(false); + + // 삭제 실패 시에도 데이터 새로고침 (안정성 향상) + setTimeout(() => { + refreshTransactions(); + }, 500); } }; @@ -82,6 +94,8 @@ const Transactions = () => { const grouped: Record = {}; transactions.forEach(transaction => { + if (!transaction.date) return; + const datePart = transaction.date.split(',')[0]; if (!grouped[datePart]) { grouped[datePart] = [];