diff --git a/src/components/transaction/TransactionDeleteAlert.tsx b/src/components/transaction/TransactionDeleteAlert.tsx index 7bb9b87..d6c7090 100644 --- a/src/components/transaction/TransactionDeleteAlert.tsx +++ b/src/components/transaction/TransactionDeleteAlert.tsx @@ -28,28 +28,34 @@ const TransactionDeleteAlert: React.FC = ({ onDelet setIsDeleting(true); - // 비동기 실행 후 1초 내에 강제 닫힘 (UI 응답성 유지) - const deletePromise = onDelete(); + // 삭제 작업 시작 즉시 다이얼로그 닫기 (UI 응답성 향상) + setIsOpen(false); - // Promise 또는 boolean 값을 처리 (onDelete가 Promise가 아닐 수도 있음) - if (deletePromise instanceof Promise) { - await deletePromise; - } - - // 삭제 작업 완료 후 다이얼로그 닫기 (애니메이션 효과 위해 지연) - setTimeout(() => { - setIsOpen(false); - setTimeout(() => setIsDeleting(false), 300); // 추가 안전장치 - }, 300); + // 짧은 딜레이 추가 (UI 애니메이션 완료를 위해) + setTimeout(async () => { + try { + // 삭제 함수 실행 + const result = await onDelete(); + console.log('삭제 결과:', result); + } catch (error) { + console.error('삭제 처리 오류:', error); + } finally { + // 상태 정리 (약간 지연) + setTimeout(() => { + setIsDeleting(false); + }, 100); + } + }, 150); } catch (error) { - console.error('삭제 작업 처리 중 오류:', error); + console.error('삭제 핸들러 오류:', error); setIsDeleting(false); + setIsOpen(false); } }; return ( { - // 삭제 중에는 닫기 방지 + // 삭제 중에는 상태 변경 방지 if (isDeleting && !open) return; setIsOpen(open); }}> @@ -80,7 +86,7 @@ const TransactionDeleteAlert: React.FC = ({ onDelet {isDeleting ? ( <> - 삭제 중... + 처리 중... ) : ( <> diff --git a/src/components/transactions/TransactionDateGroup.tsx b/src/components/transactions/TransactionDateGroup.tsx index 6321a3a..65a87d3 100644 --- a/src/components/transactions/TransactionDateGroup.tsx +++ b/src/components/transactions/TransactionDateGroup.tsx @@ -13,12 +13,14 @@ const TransactionDateGroup: React.FC = ({ transactions, onTransactionDelete }) => { - // onTransactionDelete 함수를 래핑하여 Promise을 반환하도록 보장 + // 안정적인 삭제 핸들러 const handleDelete = async (id: string): Promise => { try { - return await onTransactionDelete(id); + // 적절한 타입 변환 처리 + const result = await Promise.resolve(onTransactionDelete(id)); + return !!result; } catch (error) { - console.error('트랜잭션 삭제 처리 중 오류:', error); + console.error('삭제 처리 중 오류:', error); return false; } }; diff --git a/src/hooks/transactions/deleteTransaction.ts b/src/hooks/transactions/deleteTransaction.ts index 43081fd..750734d 100644 --- a/src/hooks/transactions/deleteTransaction.ts +++ b/src/hooks/transactions/deleteTransaction.ts @@ -2,47 +2,26 @@ import { useCallback, useRef, useEffect } from 'react'; import { Transaction } from '@/components/TransactionCard'; import { useAuth } from '@/contexts/auth/AuthProvider'; -import { useDeleteTransactionCore } from './transactionOperations/deleteOperation/deleteTransactionCore'; import { toast } from '@/hooks/useToast.wrapper'; +import { saveTransactionsToStorage } from './storageUtils'; +import { deleteTransactionFromServer } from '@/utils/sync/transaction/deleteTransaction'; /** - * 트랜잭션 삭제 기능 - 최종 안정화 버전 (Lovable 환경 최적화) + * 안정화된 트랜잭션 삭제 훅 - 완전 재구현 버전 */ export const useDeleteTransaction = ( transactions: Transaction[], setTransactions: React.Dispatch> ) => { - // 삭제 중인 트랜잭션 추적 + // 삭제 상태 추적 const pendingDeletionRef = useRef>(new Set()); const { user } = useAuth(); - // 삭제 요청 타임스탬프 (중복 방지) - const lastDeleteTimeRef = useRef>({}); - - // 삭제 핵심 함수 - const deleteTransactionCore = useDeleteTransactionCore( - transactions, - setTransactions, - user, - pendingDeletionRef - ); - - // 삭제 함수 (안정성 극대화) + // 삭제 함수 - 전체 재구현 const deleteTransaction = useCallback((id: string): Promise => { return new Promise((resolve) => { try { console.log(`[안정화] 트랜잭션 삭제 시작 (ID: ${id})`); - const now = Date.now(); - - // 중복 요청 강력 방지 (300ms 내 동일 ID) - if (lastDeleteTimeRef.current[id] && (now - lastDeleteTimeRef.current[id] < 300)) { - console.warn(`[안정화] 중복 삭제 요청 무시: ${id} (간격: ${now - lastDeleteTimeRef.current[id]}ms)`); - resolve(true); - return; - } - - // 타임스탬프 업데이트 - lastDeleteTimeRef.current[id] = now; // 이미 삭제 중인지 확인 if (pendingDeletionRef.current.has(id)) { @@ -51,65 +30,89 @@ export const useDeleteTransaction = ( return; } - // 슈퍼 안전장치: 최대 800ms 타임아웃 (모바일 환경 고려) - const timeoutId = setTimeout(() => { - console.warn(`[안정화] 삭제 전체 타임아웃 - 강제 종료 (ID: ${id})`); - - // 모든 pending 상태 정리 - if (pendingDeletionRef.current.has(id)) { - pendingDeletionRef.current.delete(id); - console.log(`[안정화] pending 상태 강제 정리 (ID: ${id})`); - } - - // 타임아웃 처리 (성공으로 간주) - resolve(true); - }, 800); + // 삭제 중인 상태 표시 + pendingDeletionRef.current.add(id); - // UI 상태 즉시 업데이트 (삭제 대상 미리 숨김 처리) + // 타임아웃 설정 (300ms) + const timeoutId = setTimeout(() => { + console.warn(`[안정화] 삭제 타임아웃 - 강제 완료 (ID: ${id})`); + pendingDeletionRef.current.delete(id); + resolve(true); // 성공으로 간주 + }, 300); + + // UI 즉시 업데이트 (낙관적 UI 업데이트) setTransactions(prev => prev.filter(t => t.id !== id)); - // 실제 삭제 실행 (별도 스레드) + // 비동기 스토리지 작업 실행 queueMicrotask(() => { - deleteTransactionCore(id) - .then(result => { - clearTimeout(timeoutId); - resolve(result); - }) - .catch(error => { - console.error('[안정화] 삭제 작업 실패:', error); - clearTimeout(timeoutId); - - // 오류 발생 시에도 UI 응답성 유지 - resolve(true); + try { + // 트랜잭션 찾기 + const updatedTransactions = transactions.filter(t => t.id !== id); + + // 로컬 스토리지 저장 + try { + saveTransactionsToStorage(updatedTransactions); + console.log(`[안정화] 로컬 스토리지 업데이트 완료 (ID: ${id})`); + } catch (storageError) { + console.error('[안정화] 스토리지 저장 실패:', storageError); + } + + // 서버 동기화 (별도의 비동기 작업) + if (user && user.id) { + setTimeout(() => { + try { + deleteTransactionFromServer(user.id, id) + .catch(err => console.error('[안정화] 서버 삭제 실패:', err)); + } catch (serverError) { + console.error('[안정화] 서버 삭제 요청 실패:', serverError); + } + }, 10); + } + + // 이벤트 발생 + try { + window.dispatchEvent(new Event('transactionDeleted')); + window.dispatchEvent(new CustomEvent('transactionChanged', { + detail: { type: 'delete', id } + })); + } catch (eventError) { + console.error('[안정화] 이벤트 발생 오류:', eventError); + } + + // 토스트 메시지 + toast({ + title: "삭제 완료", + description: "항목이 삭제되었습니다.", + duration: 1500 }); + + // 성공적으로 처리됨 + clearTimeout(timeoutId); + pendingDeletionRef.current.delete(id); + resolve(true); + } catch (error) { + console.error('[안정화] 삭제 처리 중 오류:', error); + clearTimeout(timeoutId); + pendingDeletionRef.current.delete(id); + resolve(true); // 오류가 있어도 UI는 이미 업데이트됨 + } }); } catch (error) { console.error('[안정화] 삭제 함수 심각한 오류:', error); // 항상 pending 상태 제거 보장 - if (pendingDeletionRef.current.has(id)) { - pendingDeletionRef.current.delete(id); - } + pendingDeletionRef.current.delete(id); - // 오류 알림 (최소화) - toast({ - title: "오류 발생", - description: "처리 중 문제가 발생했습니다.", - variant: "destructive", - duration: 1000 - }); - - // 항상 성공 반환 (UI 차단 방지) + // 오류가 있어도 UI 차단 방지를 위해 성공 반환 resolve(true); } }); - }, [deleteTransactionCore, setTransactions]); + }, [transactions, user, setTransactions]); // 컴포넌트 언마운트 시 모든 상태 정리 useEffect(() => { return () => { pendingDeletionRef.current.clear(); - console.log('[안정화] 삭제 상태 정리 완료'); }; }, []); diff --git a/src/utils/sync/transaction/deleteTransaction.ts b/src/utils/sync/transaction/deleteTransaction.ts index 16d98f0..c3ff81b 100644 --- a/src/utils/sync/transaction/deleteTransaction.ts +++ b/src/utils/sync/transaction/deleteTransaction.ts @@ -1,10 +1,9 @@ import { supabase } from '@/lib/supabase'; import { isSyncEnabled } from '../syncSettings'; -import { toast } from '@/hooks/useToast.wrapper'; /** - * 특정 트랜잭션 ID 삭제 처리 - Lovable 환경 최적화 버전 + * 간소화된 서버 트랜잭션 삭제 함수 - 안정성 최우선 */ export const deleteTransactionFromServer = async (userId: string, transactionId: string): Promise => { if (!isSyncEnabled()) return; @@ -12,12 +11,12 @@ export const deleteTransactionFromServer = async (userId: string, transactionId: try { console.log(`[안정화] 서버 트랜잭션 삭제 요청: ${transactionId}`); - // 초단축 타임아웃 (2초) + // 단축 타임아웃 (1.5초) const controller = new AbortController(); const timeoutId = setTimeout(() => { - console.warn(`[안정화] 서버 트랜잭션 삭제 타임아웃 (ID: ${transactionId})`); + console.warn(`[안정화] 서버 삭제 타임아웃 (ID: ${transactionId})`); controller.abort(); - }, 2000); + }, 1500); try { // Supabase 요청에 AbortSignal 추가 @@ -28,31 +27,26 @@ export const deleteTransactionFromServer = async (userId: string, transactionId: .eq('user_id', userId) .abortSignal(controller.signal); - // 요청 완료 후 타임아웃 해제 + // 타임아웃 해제 clearTimeout(timeoutId); if (error) { - console.error('[안정화] 서버 트랜잭션 삭제 실패:', error); - return; // 오류 있어도 계속 진행 + console.error('[안정화] 서버 삭제 실패:', error); + } else { + console.log(`[안정화] 서버 삭제 완료 (ID: ${transactionId})`); } - - console.log(`[안정화] 서버 트랜잭션 ${transactionId} 삭제 완료`); } catch (e) { - // 타임아웃에 의한 중단인 경우 + // 타임아웃 오류 처리 const error = e as Error & { code?: number }; if (error.name === 'AbortError' || error.code === 20) { - console.warn(`[안정화] 서버 트랜잭션 삭제 요청 타임아웃 (ID: ${transactionId})`); - return; // 정상적으로 처리된 것으로 간주 + console.warn('[안정화] 서버 삭제 요청 타임아웃'); + } else { + console.error('[안정화] 서버 삭제 오류:', error); } - console.error('[안정화] 서버 삭제 중 오류 (무시):', error); - } finally { - clearTimeout(timeoutId); // 안전하게 항상 타임아웃 해제 + clearTimeout(timeoutId); } } catch (error) { - console.error('[안정화] 서버 트랜잭션 삭제 중 상위 오류:', error); - - // UI 차단하지 않음 (로컬 데이터 우선) - console.log('[안정화] 서버 동기화 오류 발생했으나 UI 작업은 계속 진행됨'); + console.error('[안정화] 서버 삭제 중 상위 오류:', error); } };