Fix transaction deletion issue

Addresses the issue where deleting transactions caused the homepage and expense page to freeze.
This commit is contained in:
gpt-engineer-app[bot]
2025-03-18 05:52:14 +00:00
parent 281e459f14
commit df60d11aa5
4 changed files with 142 additions and 91 deletions

View File

@@ -1,5 +1,5 @@
import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useRef } from 'react';
import { Transaction } from './TransactionCard';
import TransactionEditDialog from './TransactionEditDialog';
import { ChevronRight } from 'lucide-react';
@@ -22,6 +22,15 @@ const RecentTransactionsSection: React.FC<RecentTransactionsSectionProps> = ({
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
const { updateTransaction, deleteTransaction } = useBudget();
// 삭제 중인 ID 추적
const deletingIdRef = useRef<string | null>(null);
// 타임아웃 추적
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
// 삭제 요청 타임스탬프 추적 (급발진 방지)
const lastDeleteTimeRef = useRef<Record<string, number>>({});
const handleTransactionClick = (transaction: Transaction) => {
setSelectedTransaction(transaction);
@@ -40,42 +49,56 @@ const RecentTransactionsSection: React.FC<RecentTransactionsSectionProps> = ({
const handleDeleteTransaction = useCallback(async (id: string): Promise<boolean> => {
try {
// 이미 삭제 중인 경우 중복 요청 방지
if (isDeleting) {
if (isDeleting || deletingIdRef.current === id) {
console.warn('이미 삭제 작업이 진행 중입니다');
return true;
}
// 급발진 방지 (300ms 내 동일 ID 연속 호출 차단)
const now = Date.now();
if (lastDeleteTimeRef.current[id] && (now - lastDeleteTimeRef.current[id] < 300)) {
console.warn('삭제 요청이 너무 빠릅니다. 무시합니다.');
return true;
}
// 타임스탬프 업데이트
lastDeleteTimeRef.current[id] = now;
// 삭제 상태 설정
setIsDeleting(true);
deletingIdRef.current = id;
// 먼저 다이얼로그 닫기 (UI 응답성 확보)
setIsDialogOpen(false);
// 3초 타임아웃 설정 (UI 멈춤 방지)
const timeoutPromise = new Promise<boolean>(resolve => {
const timeout = setTimeout(() => {
console.warn('삭제 타임아웃 - 강제 완료');
setIsDeleting(false);
resolve(true);
}, 3000);
return () => clearTimeout(timeout);
});
// 타임아웃 생성 (1.5초 제한)
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
// 실제 삭제 요청
const deletePromise = (async () => {
try {
deleteTransaction(id);
return true;
} catch (error) {
console.error('삭제 요청 실패:', error);
return false;
}
})();
// 안전 장치: 1.5초 후 상태 강제 초기화
timeoutRef.current = setTimeout(() => {
console.warn('삭제 타임아웃 - 강제 상태 초기화');
setIsDeleting(false);
deletingIdRef.current = null;
}, 1500);
// 둘 중 먼저 완료되는 것으로 처리
await Promise.race([deletePromise, timeoutPromise]);
// 삭제 요청 실행 및 즉시 리턴
try {
// 컨텍스트를 통한 삭제 요청
deleteTransaction(id);
console.log('삭제 요청 전송 완료');
} catch (error) {
console.error('삭제 요청 실패:', error);
}
// 즉시 정상 반환 (트랜잭션은 비동기식으로 처리)
// 0.5초 후 상태 초기화
setTimeout(() => {
setIsDeleting(false);
deletingIdRef.current = null;
}, 500);
setIsDeleting(false);
return true;
} catch (error) {
console.error('트랜잭션 삭제 중 오류:', error);
@@ -83,6 +106,12 @@ const RecentTransactionsSection: React.FC<RecentTransactionsSectionProps> = ({
// 항상 상태 정리
setIsDeleting(false);
setIsDialogOpen(false);
deletingIdRef.current = null;
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
timeoutRef.current = null;
}
toast({
title: "삭제 실패",
@@ -94,6 +123,15 @@ const RecentTransactionsSection: React.FC<RecentTransactionsSectionProps> = ({
return false;
}
}, [deleteTransaction, isDeleting]);
// 컴포넌트 언마운트 시 타임아웃 정리
React.useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);
const formatCurrency = (amount: number) => {
return amount.toLocaleString('ko-KR') + '원';
@@ -147,3 +185,4 @@ const RecentTransactionsSection: React.FC<RecentTransactionsSectionProps> = ({
};
export default RecentTransactionsSection;