From 27b4e3274e29c294f9b1f47104870486955925ca Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 03:51:17 +0000 Subject: [PATCH] Fix transaction deletion issue Addresses the issue where deleting a transaction in the transaction history would cause the application to freeze. --- .../transaction/TransactionDeleteAlert.tsx | 44 +++++-- .../deleteOperation/deleteTransactionCore.ts | 116 +++++++----------- .../deleteTransaction.ts | 35 +++++- src/pages/Transactions.tsx | 30 +++-- 4 files changed, 126 insertions(+), 99 deletions(-) diff --git a/src/components/transaction/TransactionDeleteAlert.tsx b/src/components/transaction/TransactionDeleteAlert.tsx index dc7962c..d491597 100644 --- a/src/components/transaction/TransactionDeleteAlert.tsx +++ b/src/components/transaction/TransactionDeleteAlert.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Button } from '@/components/ui/button'; -import { Trash2 } from 'lucide-react'; +import { Trash2, Loader2 } from 'lucide-react'; import { AlertDialog, AlertDialogAction, @@ -19,15 +19,30 @@ interface TransactionDeleteAlertProps { } const TransactionDeleteAlert: React.FC = ({ onDelete }) => { + const [isOpen, setIsOpen] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + + const handleDelete = async () => { + try { + setIsDeleting(true); + await onDelete(); + setIsOpen(false); + } catch (error) { + console.error('삭제 작업 처리 중 오류:', error); + } finally { + setIsDeleting(false); + } + }; + return ( - + @@ -39,13 +54,24 @@ const TransactionDeleteAlert: React.FC = ({ onDelet - 취소 - 취소 + diff --git a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts index 8c39059..53149e5 100644 --- a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts +++ b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts @@ -6,7 +6,7 @@ import { handleDeleteStorage } from './deleteTransactionStorage'; import { sortTransactionsByDate } from './deleteTransactionUtils'; /** - * 트랜잭션 삭제 핵심 기능 + * 트랜잭션 삭제 핵심 기능 - 성능 및 안정성 개선 */ export const useDeleteTransactionCore = ( transactions: Transaction[], @@ -14,37 +14,31 @@ export const useDeleteTransactionCore = ( user: any, pendingDeletionRef: MutableRefObject> ) => { - // 트랜잭션 삭제 - 안정성과 성능 개선 버전 + // 트랜잭션 삭제 - 성능 및 안정성 개선 버전 return useCallback((id: string): Promise => { // pendingDeletionRef 초기화 확인 if (!pendingDeletionRef.current) { pendingDeletionRef.current = new Set(); } - // 프로미스 객체와 취소 함수 참조를 위한 변수 선언 - let promiseObj: Promise & { cancel?: () => void }; - - // 기존 promise를 변수로 저장해서 참조 가능하게 함 - promiseObj = new Promise((resolve, reject) => { - // 삭제 작업 취소 플래그 초기화 - 프로미스 내부에서 선언 - let isCanceled = false; - + // 삭제 작업 중복 방지 + if (pendingDeletionRef.current.has(id)) { + console.log('이미 삭제 중인 트랜잭션:', id); + return Promise.resolve(false); + } + + // 트랜잭션이 존재하는지 확인 + const transactionToDelete = transactions.find(t => t.id === id); + if (!transactionToDelete) { + console.warn('삭제할 트랜잭션이 존재하지 않음:', id); + return Promise.resolve(false); + } + + // 프로미스 생성 + return new Promise((resolve) => { try { console.log('트랜잭션 삭제 작업 시작 - ID:', id); - // 이미 삭제 중인 트랜잭션인지 확인 - if (pendingDeletionRef.current.has(id)) { - console.warn('이미 삭제 중인 트랜잭션입니다:', id); - return resolve(false); - } - - // 삭제할 트랜잭션이 존재하는지 확인 및 데이터 복사 보관 - const transactionToDelete = transactions.find(t => t.id === id); - if (!transactionToDelete) { - console.warn('삭제할 트랜잭션이 존재하지 않음:', id); - return resolve(false); - } - // 삭제 중인 상태로 표시 pendingDeletionRef.current.add(id); @@ -54,45 +48,36 @@ export const useDeleteTransactionCore = ( // UI 업데이트 - 동기식 처리 setTransactions(updatedTransactions); - // 사용자 인터페이스 응답성 감소 전에 이벤트 발생 처리 - try { - // 상태 업데이트 바로 후 이벤트 발생 - setTimeout(() => { - try { - window.dispatchEvent(new Event('transactionUpdated')); - } catch (innerError) { - console.warn('이벤트 발생 중 비치명적 오류:', innerError); - } - }, 0); - } catch (eventError) { - console.warn('이벤트 디스패치 설정 오류:', eventError); - } - - // 백그라운드 작업 처리 - setTimeout(() => { - if (isCanceled) { - console.log('작업이 취소되었습니다.'); - return; - } - - // 스토리지 처리 - handleDeleteStorage( - isCanceled, - updatedTransactions, - id, - user, - transactionToDelete, - pendingDeletionRef - ); - - // 작업 완료 후 보류 중인 삭제 목록에서 제거 - pendingDeletionRef.current?.delete(id); - }, 50); - - // 상태 업데이트가 이미 수행되었으므로 즉시 성공 반환 - console.log('트랜잭션 삭제 UI 업데이트 완료'); + // UI 업데이트 완료 후 즉시 성공 반환 (사용자 경험 개선) resolve(true); + // 백그라운드에서 스토리지 작업 처리 + setTimeout(() => { + try { + // 스토리지 처리 + handleDeleteStorage( + false, // 취소 상태 없음 + updatedTransactions, + id, + user, + transactionToDelete, + pendingDeletionRef + ); + } catch (storageError) { + console.error('스토리지 작업 오류:', storageError); + } finally { + // 작업 완료 후 보류 중인 삭제 목록에서 제거 + pendingDeletionRef.current?.delete(id); + + // 트랜잭션 삭제 완료 이벤트 발생 + try { + window.dispatchEvent(new Event('transactionDeleted')); + } catch (e) { + console.warn('이벤트 발생 오류:', e); + } + } + }, 50); + } catch (error) { console.error('트랜잭션 삭제 초기화 중 오류:', error); @@ -106,17 +91,8 @@ export const useDeleteTransactionCore = ( // 캣치된 모든 오류에서 보류 삭제 표시 제거 pendingDeletionRef.current?.delete(id); - reject(error); + resolve(false); } - - // cancel 함수를 프로미스 객체에 연결 (프로미스 내부에서) - promiseObj.cancel = () => { - isCanceled = true; - pendingDeletionRef.current?.delete(id); - console.log('트랜잭션 삭제 작업 취소 완료'); - }; }); - - return promiseObj; }, [transactions, setTransactions, user]); }; diff --git a/src/hooks/transactions/transactionOperations/deleteTransaction.ts b/src/hooks/transactions/transactionOperations/deleteTransaction.ts index f8d1694..3918b21 100644 --- a/src/hooks/transactions/transactionOperations/deleteTransaction.ts +++ b/src/hooks/transactions/transactionOperations/deleteTransaction.ts @@ -6,7 +6,7 @@ import { useDeleteTransactionCore } from './deleteOperation/deleteTransactionCor import { toast } from '@/hooks/useToast.wrapper'; /** - * 트랜잭션 삭제 기능 + * 트랜잭션 삭제 기능 - 성능 및 안정성 개선 * 로컬 스토리지와 Supabase에서 트랜잭션을 삭제합니다. */ export const useDeleteTransaction = ( @@ -16,21 +16,24 @@ export const useDeleteTransaction = ( // 현재 진행 중인 삭제 작업 추적을 위한 ref const pendingDeletionRef = useRef>(new Set()); const { user } = useAuth(); + + // 삭제 작업 타임아웃 관리를 위한 ref + const timeoutRef = useRef>({}); // 핵심 삭제 로직 사용 const deleteTransactionHandler = useDeleteTransactionCore(transactions, setTransactions, user, pendingDeletionRef); - // 디버깅 및 안정성 추가 + // 성능 및 안정성 개선 버전 const deleteTransaction = useCallback(async (id: string) => { console.log('트랜잭션 삭제 시작:', id); try { - // 이미 삭제 중인지 확인 + // 이미 삭제 중인지 확인 (중복 호출 방지) if (pendingDeletionRef.current.has(id)) { console.log('이미 삭제 중인 트랜잭션:', id); toast({ title: "처리 중", - description: "이전 삭제 작업이 진행 중입니다. 잠시 기다려주세요.", + description: "이전 삭제 작업이 진행 중입니다.", duration: 1500 }); return false; @@ -51,9 +54,23 @@ export const useDeleteTransaction = ( // 삭제 실행 (비동기 처리) const result = await deleteTransactionHandler(id); + + // 안전장치: 삭제 작업이 10초 이상 걸리면 강제로 상태 초기화 + timeoutRef.current[id] = setTimeout(() => { + if (pendingDeletionRef.current.has(id)) { + console.warn('삭제 작업 타임아웃 - 강제 초기화:', id); + pendingDeletionRef.current.delete(id); + delete timeoutRef.current[id]; + } + }, 10000); + return result; } catch (error) { console.error('트랜잭션 삭제 오류:', error); + + // 오류 발생 시 상태 초기화 + pendingDeletionRef.current.delete(id); + toast({ title: "삭제 실패", description: "처리 중 오류가 발생했습니다. 다시 시도해주세요.", @@ -64,5 +81,15 @@ export const useDeleteTransaction = ( } }, [transactions, deleteTransactionHandler]); + // 컴포넌트 언마운트 시 타임아웃 정리 + useEffect(() => { + return () => { + // 모든 타임아웃 정리 + Object.values(timeoutRef.current).forEach(timeout => { + clearTimeout(timeout); + }); + }; + }, []); + return deleteTransaction; }; diff --git a/src/pages/Transactions.tsx b/src/pages/Transactions.tsx index 1c9a721..427e99c 100644 --- a/src/pages/Transactions.tsx +++ b/src/pages/Transactions.tsx @@ -25,7 +25,6 @@ const Transactions = () => { const { budgetData } = useBudget(); const [isDataLoaded, setIsDataLoaded] = useState(false); const [isProcessing, setIsProcessing] = useState(false); - // 트랜잭션 삭제 중인 ID 추적 const [deletingId, setDeletingId] = useState(null); // 데이터 로드 상태 관리 @@ -35,10 +34,10 @@ const Transactions = () => { } }, [budgetData, isLoading]); - // 트랜잭션 삭제 핸들러 (안정성 개선) + // 트랜잭션 삭제 핸들러 (성능 및 안정성 개선) const handleTransactionDelete = async (id: string) => { - // 이미 처리 중인 삭제 작업이 있다면 취소 - if (isProcessing || deletingId) { + // 이미 처리 중인 경우 작업 무시 + if (isProcessing || deletingId === id) { console.log('이미 삭제 작업이 진행 중입니다:', deletingId); return; } @@ -50,22 +49,19 @@ const Transactions = () => { setIsProcessing(true); setDeletingId(id); - // 트랜잭션 삭제 수행 + // 비동기 삭제 작업 실행 const success = await deleteTransaction(id); - // 일정 시간 후 처리 상태 해제 (UI 응답성 향상) + // 데이터 새로고침 (UI 응답성 향상을 위해 약간 지연) setTimeout(() => { + refreshTransactions(); + + // 상태 초기화 setIsProcessing(false); setDeletingId(null); - - // 삭제 성공 시 데이터 새로고침 - if (success) { - console.log('삭제 성공, 데이터 새로고침'); - setTimeout(() => { - refreshTransactions(); - }, 300); - } - }, 800); + }, 300); + + return success; } catch (error) { console.error('트랜잭션 삭제 처리 중 오류:', error); @@ -73,10 +69,12 @@ const Transactions = () => { setIsProcessing(false); setDeletingId(null); - // 오류 후에도 데이터 새로고침 (안정성 향상) + // 오류 후에도 데이터 새로고침 (일관성 유지) setTimeout(() => { refreshTransactions(); }, 500); + + return false; } };