From 2b8069a150ccd578169e1937b13b34ac05a8ac70 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 04:12:54 +0000 Subject: [PATCH] Review expense history page --- src/components/TransactionEditDialog.tsx | 65 +++++++------------ .../deleteOperation/deleteTransactionCore.ts | 34 ++++++---- .../deleteTransactionStorage.ts | 55 +++------------- .../deleteTransaction.ts | 40 ++++-------- src/pages/Transactions.tsx | 16 +++-- 5 files changed, 75 insertions(+), 135 deletions(-) diff --git a/src/components/TransactionEditDialog.tsx b/src/components/TransactionEditDialog.tsx index 06992bf..6c3148e 100644 --- a/src/components/TransactionEditDialog.tsx +++ b/src/components/TransactionEditDialog.tsx @@ -29,7 +29,7 @@ interface TransactionEditDialogProps { open: boolean; onOpenChange: (open: boolean) => void; onSave?: (updatedTransaction: Transaction) => void; - onDelete?: (id: string) => void; + onDelete?: (id: string) => Promise | boolean; } const TransactionEditDialog: React.FC = ({ @@ -40,6 +40,7 @@ const TransactionEditDialog: React.FC = ({ onDelete }) => { const { updateTransaction, deleteTransaction } = useBudget(); + const isMobile = useIsMobile(); const form = useForm({ @@ -78,48 +79,28 @@ const TransactionEditDialog: React.FC = ({ }); }; - const handleDelete = (): Promise => { - return new Promise((resolve) => { - try { - // 다이얼로그 닫기를 먼저 수행 (UI 블로킹 방지) - onOpenChange(false); - - // 잠시 지연 후 삭제 작업 수행 (안정성 향상) - setTimeout(() => { - try { - // 트랜잭션 ID 임시 저장 (안전성 확보) - const transactionId = transaction.id; - - // 부모 컴포넌트의 onDelete 콜백이 있다면 호출 - if (onDelete) { - onDelete(transactionId); - } - - // 컨텍스트를 통해 트랜잭션 삭제 - deleteTransaction(transactionId); - - console.log('트랜잭션 삭제 완료:', transactionId); - resolve(true); - } catch (innerError) { - console.error('트랜잭션 삭제 중 내부 오류:', innerError); - toast({ - title: "삭제 실패", - description: "지출 항목을 삭제하는데 문제가 발생했습니다.", - variant: "destructive" - }); - resolve(false); - } - }, 100); - } catch (outerError) { - console.error('트랜잭션 삭제 처리 중 오류:', outerError); - toast({ - title: "시스템 오류", - description: "처리 중 문제가 발생했습니다. 다시 시도해주세요.", - variant: "destructive" - }); - resolve(false); + const handleDelete = async (): Promise => { + try { + // 다이얼로그 닫기를 먼저 수행 (UI 블로킹 방지) + onOpenChange(false); + + // 삭제 처리 - 부모 컴포넌트의 onDelete 콜백이 있다면 호출 + if (onDelete) { + return await onDelete(transaction.id); } - }); + + // 부모 컴포넌트에서 처리하지 않은 경우 기본 처리 + deleteTransaction(transaction.id); + return true; + } catch (error) { + console.error('트랜잭션 삭제 중 오류:', error); + toast({ + title: "삭제 실패", + description: "지출 항목을 삭제하는데 문제가 발생했습니다.", + variant: "destructive" + }); + return false; + } }; return ( diff --git a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts index 3ce750c..df13aa3 100644 --- a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts +++ b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionCore.ts @@ -25,6 +25,18 @@ export const useDeleteTransactionCore = ( pendingDeletionRef.current = new Set(); } + // 이미 삭제 중인지 확인 + if (pendingDeletionRef.current.has(id)) { + console.log('이미 삭제 중인 트랜잭션:', id); + toast({ + title: "처리 중", + description: "이전 삭제 작업이 진행 중입니다.", + duration: 1500 + }); + resolve(false); + return; + } + // 트랜잭션이 존재하는지 확인 const transactionToDelete = transactions.find(t => t.id === id); if (!transactionToDelete) { @@ -42,35 +54,31 @@ export const useDeleteTransactionCore = ( // UI 업데이트 - 동기식 처리 setTransactions(updatedTransactions); + // 삭제 성공 토스트 + toast({ + title: "삭제 완료", + description: "지출 항목이 삭제되었습니다.", + duration: 2000 + }); + // UI 업데이트 완료 후 즉시 성공 반환 (사용자 경험 개선) resolve(true); - // 백그라운드에서 스토리지 작업 처리 (setTimeout 사용해 메인 스레드 차단 방지) + // 백그라운드에서 스토리지 작업 처리 setTimeout(() => { try { - // 스토리지 처리 + // 스토리지 처리 - 간소화된 함수 호출 handleDeleteStorage( - false, // 취소 상태 없음 updatedTransactions, id, user, - transactionToDelete, pendingDeletionRef ); } catch (storageError) { console.error('스토리지 작업 오류:', storageError); - } finally { - // 작업 완료 후 보류 중인 삭제 목록에서 제거 if (pendingDeletionRef.current) { pendingDeletionRef.current.delete(id); } - - // 트랜잭션 삭제 완료 이벤트 발생 - try { - window.dispatchEvent(new Event('transactionDeleted')); - } catch (e) { - console.warn('이벤트 발생 오류:', e); - } } }, 50); diff --git a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts index 8cda714..a8c89f2 100644 --- a/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts +++ b/src/hooks/transactions/transactionOperations/deleteOperation/deleteTransactionStorage.ts @@ -4,82 +4,55 @@ import { Transaction } from '@/components/TransactionCard'; import { saveTransactionsToStorage } from '../../storageUtils'; import { deleteTransactionFromSupabase } from '../../supabaseUtils'; import { toast } from '@/hooks/useToast.wrapper'; -import { normalizeDate } from '@/utils/sync/transaction/dateUtils'; /** - * 스토리지 및 Supabase 삭제 처리 + * 스토리지 및 Supabase 삭제 처리 - 간소화 및 안정성 개선 */ export const handleDeleteStorage = ( - isCanceled: boolean, updatedTransactions: Transaction[], id: string, user: any, - transactionToDelete: Transaction, pendingDeletionRef: MutableRefObject> ) => { try { - if (isCanceled) { - console.log('백그라운드 작업이 취소되었습니다.'); - return; - } - // 로컬 스토리지 업데이트 saveTransactionsToStorage(updatedTransactions); console.log('로컬 스토리지 저장 완료'); // Supabase 업데이트 (비동기 처리) if (user) { - // 동기적 에러를 피하기 위해 setTimeout으로 감싸기 + // 네트워크 작업은 비동기로 진행 setTimeout(() => { try { - // 동기화 작업 실행 deleteTransactionFromSupabase(user, id) .catch(error => { console.error('Supabase 삭제 오류:', error); }) .finally(() => { - // 삭제 완료 후 토스트 표시 (취소되지 않은 경우만) - if (!isCanceled && pendingDeletionRef.current) { + // 작업 완료 표시 + if (pendingDeletionRef.current) { pendingDeletionRef.current.delete(id); - - // 사용자 피드백 - toast({ - title: "삭제 완료", - description: "지출 항목이 삭제되었습니다.", - duration: 2000 - }); } }); } catch (e) { - console.error('Supabase 작업 초기화 오류:', e); + console.error('Supabase 작업 오류:', e); + // 작업 완료 표시 if (pendingDeletionRef.current) { pendingDeletionRef.current.delete(id); } } }, 10); } else { - // 사용자가 없는 경우 토스트 표시 - if (!isCanceled) { - toast({ - title: "삭제 완료", - description: "지출 항목이 삭제되었습니다.", - duration: 2000 - }); - } - // 작업 완료 표시 if (pendingDeletionRef.current) { pendingDeletionRef.current.delete(id); } } - // 삭제 완료 이벤트 발생 - 지연 처리로 안정성 향상 + // 이벤트 발생 setTimeout(() => { try { - if (!isCanceled) { - console.log('트랜잭션 삭제 완료 이벤트 발생'); - window.dispatchEvent(new Event('transactionDeleted')); - } + window.dispatchEvent(new Event('transactionDeleted')); } catch (e) { console.error('이벤트 발생 오류:', e); } @@ -92,17 +65,5 @@ export const handleDeleteStorage = ( if (pendingDeletionRef.current) { pendingDeletionRef.current.delete(id); } - - // 삭제 실패 알림 - if (!isCanceled) { - toast({ - title: "삭제 중 오류", - description: "지출 항목 삭제 중 문제가 발생했습니다.", - variant: "destructive", - duration: 2000 - }); - } } }; - -// 중복 정의된 함수 제거 - 이미 import로 가져오고 있으므로 여기서 중복 선언할 필요가 없습니다. diff --git a/src/hooks/transactions/transactionOperations/deleteTransaction.ts b/src/hooks/transactions/transactionOperations/deleteTransaction.ts index c46de5b..d56322f 100644 --- a/src/hooks/transactions/transactionOperations/deleteTransaction.ts +++ b/src/hooks/transactions/transactionOperations/deleteTransaction.ts @@ -1,3 +1,4 @@ + import { useCallback, useRef, useEffect } from 'react'; import { Transaction } from '@/components/TransactionCard'; import { useAuth } from '@/contexts/auth/AuthProvider'; @@ -6,7 +7,6 @@ import { toast } from '@/hooks/useToast.wrapper'; /** * 트랜잭션 삭제 기능 - 성능 및 안정성 개선 - * 로컬 스토리지와 Supabase에서 트랜잭션을 삭제합니다. */ export const useDeleteTransaction = ( transactions: Transaction[], @@ -20,43 +20,30 @@ export const useDeleteTransaction = ( const timeoutRef = useRef>({}); // 핵심 삭제 로직 사용 - const deleteTransactionHandler = useDeleteTransactionCore(transactions, setTransactions, user, pendingDeletionRef); + const deleteTransactionHandler = useDeleteTransactionCore( + transactions, + setTransactions, + user, + pendingDeletionRef + ); // 성능 및 안정성 개선 버전 - const deleteTransaction = useCallback(async (id: string) => { + const deleteTransaction = useCallback(async (id: string): Promise => { console.log('트랜잭션 삭제 시작:', id); try { - // 이미 삭제 중인지 확인 (중복 호출 방지) - if (pendingDeletionRef.current?.has(id)) { + // 중복 삭제 방지 확인 + if (pendingDeletionRef.current.has(id)) { console.log('이미 삭제 중인 트랜잭션:', id); - toast({ - title: "처리 중", - description: "이전 삭제 작업이 진행 중입니다.", - duration: 1500 - }); return false; } - // 트랜잭션이 존재하는지 확인 - const transactionExists = transactions.some(t => t.id === id); - if (!transactionExists) { - console.error('존재하지 않는 트랜잭션 ID:', id); - toast({ - title: "삭제 실패", - description: "해당 지출 항목을 찾을 수 없습니다.", - variant: "destructive", - duration: 1500 - }); - return false; - } - - // 삭제 실행 (비동기 처리) + // 트랜잭션 삭제 실행 const result = await deleteTransactionHandler(id); // 안전장치: 삭제 작업이 10초 이상 걸리면 강제로 상태 초기화 timeoutRef.current[id] = setTimeout(() => { - if (pendingDeletionRef.current?.has(id)) { + if (pendingDeletionRef.current.has(id)) { console.warn('삭제 작업 타임아웃 - 강제 초기화:', id); pendingDeletionRef.current.delete(id); delete timeoutRef.current[id]; @@ -78,9 +65,10 @@ export const useDeleteTransaction = ( variant: "destructive", duration: 1500 }); + return false; } - }, [transactions, deleteTransactionHandler]); + }, [deleteTransactionHandler]); // 컴포넌트 언마운트 시 타임아웃 정리 useEffect(() => { diff --git a/src/pages/Transactions.tsx b/src/pages/Transactions.tsx index 427e99c..ca4a6bd 100644 --- a/src/pages/Transactions.tsx +++ b/src/pages/Transactions.tsx @@ -34,12 +34,12 @@ const Transactions = () => { } }, [budgetData, isLoading]); - // 트랜잭션 삭제 핸들러 (성능 및 안정성 개선) + // 트랜잭션 삭제 핸들러 - 간소화 및 안정성 개선 const handleTransactionDelete = async (id: string) => { // 이미 처리 중인 경우 작업 무시 if (isProcessing || deletingId === id) { console.log('이미 삭제 작업이 진행 중입니다:', deletingId); - return; + return false; } try { @@ -49,18 +49,20 @@ const Transactions = () => { setIsProcessing(true); setDeletingId(id); - // 비동기 삭제 작업 실행 + // 간소화된 삭제 작업 실행 const success = await deleteTransaction(id); - // 데이터 새로고침 (UI 응답성 향상을 위해 약간 지연) + // 상태 초기화 setTimeout(() => { - refreshTransactions(); - - // 상태 초기화 setIsProcessing(false); setDeletingId(null); }, 300); + // 성공 여부와 관계없이 데이터 새로고침 + setTimeout(() => { + refreshTransactions(); + }, 500); + return success; } catch (error) { console.error('트랜잭션 삭제 처리 중 오류:', error);