diff --git a/src/components/TransactionEditDialog.tsx b/src/components/TransactionEditDialog.tsx index d1651a6..cef8565 100644 --- a/src/components/TransactionEditDialog.tsx +++ b/src/components/TransactionEditDialog.tsx @@ -79,19 +79,42 @@ const TransactionEditDialog: React.FC = ({ }; const handleDelete = () => { - // 다이얼로그 닫기를 먼저 수행 (UI 블로킹 방지) - onOpenChange(false); - - // 약간의 지연 후 삭제 작업 수행 (안정성 향상) - setTimeout(() => { - // 컨텍스트를 통해 트랜잭션 삭제 - deleteTransaction(transaction.id); + try { + // 다이얼로그 닫기를 먼저 수행 (UI 블로킹 방지) + onOpenChange(false); - // 부모 컴포넌트의 onDelete 콜백이 있다면 호출 - if (onDelete) { - onDelete(transaction.id); - } - }, 100); + // 잠시 지연 후 삭제 작업 수행 (안정성 향상) + setTimeout(() => { + try { + // 트랜잭션 ID 임시 저장 (안전성 확보) + const transactionId = transaction.id; + + // 부모 컴포넌트의 onDelete 콜백이 있다면 호출 + if (onDelete) { + onDelete(transactionId); + } + + // 컨텍스트를 통해 트랜잭션 삭제 + deleteTransaction(transactionId); + + console.log('트랜잭션 삭제 완료:', transactionId); + } catch (innerError) { + console.error('트랜잭션 삭제 중 내부 오류:', innerError); + toast({ + title: "삭제 실패", + description: "지출 항목을 삭제하는데 문제가 발생했습니다.", + variant: "destructive" + }); + } + }, 100); + } catch (outerError) { + console.error('트랜잭션 삭제 처리 중 오류:', outerError); + toast({ + title: "시스템 오류", + description: "처리 중 문제가 발생했습니다. 다시 시도해주세요.", + variant: "destructive" + }); + } }; return ( diff --git a/src/hooks/transactions/filterOperations/index.ts b/src/hooks/transactions/filterOperations/index.ts index fcfab09..2f09d8c 100644 --- a/src/hooks/transactions/filterOperations/index.ts +++ b/src/hooks/transactions/filterOperations/index.ts @@ -1,12 +1,12 @@ +import { useCallback } from 'react'; +import { FilteringProps, FilteringReturn } from './types'; import { useMonthSelection } from './useMonthSelection'; import { useFilterApplication } from './useFilterApplication'; import { useTotalCalculation } from './useTotalCalculation'; -import { FilteringProps, FilteringReturn } from './types'; /** - * 트랜잭션 필터링 작업 관련 훅 - * 월 선택, 필터 적용, 총액 계산 기능을 제공합니다. + * 트랜잭션 필터링 관련 기능을 통합한 훅 */ export const useTransactionsFiltering = ({ transactions, @@ -16,27 +16,32 @@ export const useTransactionsFiltering = ({ setFilteredTransactions }: FilteringProps): FilteringReturn => { // 월 선택 관련 기능 - const { handlePrevMonth, handleNextMonth } = useMonthSelection( + const { handlePrevMonth, handleNextMonth } = useMonthSelection({ selectedMonth, setSelectedMonth - ); + }); - // 필터 적용 - useFilterApplication( + // 필터 적용 관련 기능 + const { filterTransactions } = useFilterApplication({ transactions, selectedMonth, searchQuery, setFilteredTransactions - ); + }); - // 총 지출 계산 + // 총 지출 계산 관련 기능 const { getTotalExpenses } = useTotalCalculation(); + // 강제 필터링 실행 함수 (외부에서 호출 가능) + const forceRefresh = useCallback(() => { + console.log('필터 강제 새로고침'); + filterTransactions(); + }, [filterTransactions]); + return { handlePrevMonth, handleNextMonth, - getTotalExpenses + getTotalExpenses, + forceRefresh }; }; - -export * from './types'; diff --git a/src/hooks/transactions/filterOperations/useFilterApplication.ts b/src/hooks/transactions/filterOperations/useFilterApplication.ts index 28e63b1..8e0a1e5 100644 --- a/src/hooks/transactions/filterOperations/useFilterApplication.ts +++ b/src/hooks/transactions/filterOperations/useFilterApplication.ts @@ -1,32 +1,88 @@ -import { useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import { Transaction } from '@/components/TransactionCard'; -import { - filterTransactionsByMonth, - filterTransactionsByQuery -} from '../filterUtils'; +import { FilteringProps } from './types'; /** - * 필터 적용 관련 훅 - * 트랜잭션에 대한 월별/검색어 필터링을 적용합니다. + * 거래 필터링 로직 + * 선택된 월과 검색어를 기준으로 거래를 필터링합니다. */ -export const useFilterApplication = ( - transactions: Transaction[], - selectedMonth: string, - searchQuery: string, - setFilteredTransactions: (transactions: Transaction[]) => void -) => { - // 필터 적용 - useEffect(() => { - // 1. 월별 필터링 - let filtered = filterTransactionsByMonth(transactions, selectedMonth); - - // 2. 검색어 필터링 - if (searchQuery.trim()) { - filtered = filterTransactionsByQuery(filtered, searchQuery); +export const useFilterApplication = ({ + transactions, + selectedMonth, + searchQuery, + setFilteredTransactions +}: Pick) => { + + // 거래 필터링 함수 + const filterTransactions = useCallback(() => { + try { + // 현재 연도 가져오기 + const currentYear = new Date().getFullYear(); + + // 선택된 월에 대한 데이터 필터링 + const [selectedMonthName, selectedMonthNumber] = selectedMonth.split(' '); + const monthToFilter = parseInt(selectedMonthNumber); + + // 월별 필터링 + let filtered = transactions.filter(transaction => { + if (!transaction.date) return false; + + // 직접 저장된 date 문자열에서 날짜 추출 시도 + try { + if (transaction.date.includes('오늘')) { + // '오늘, HH:MM' 형식인 경우 현재 월로 간주 + const today = new Date(); + return today.getMonth() + 1 === monthToFilter; + } else if (transaction.date.includes('년')) { + // 'YYYY년 MM월 DD일' 형식인 경우 + const monthPart = transaction.date.split('년')[1]?.trim().split('월')[0]; + if (monthPart) { + return parseInt(monthPart) === monthToFilter; + } + return false; + } else { + // ISO 문자열 또는 다른 표준 형식으로 저장된 경우 + const date = new Date(transaction.date); + if (!isNaN(date.getTime())) { + return date.getMonth() + 1 === monthToFilter; + } + return false; + } + } catch (e) { + console.error('날짜 파싱 오류:', e, transaction.date); + return false; + } + }); + + console.log(`월별 필터링: ${selectedMonth} 트랜잭션 수: ${filtered.length}`); + + // 검색어에 따른 필터링 (추가) + if (searchQuery.trim()) { + const searchLower = searchQuery.toLowerCase(); + filtered = filtered.filter(transaction => + transaction.title.toLowerCase().includes(searchLower) || + transaction.category.toLowerCase().includes(searchLower) || + transaction.amount.toString().includes(searchQuery) + ); + } + + // 필터링된 거래 설정 + setFilteredTransactions(filtered); + console.log(`필터링 결과: ${filtered.length} 트랜잭션`); + } catch (error) { + console.error('거래 필터링 중 오류:', error); + // 오류 발생 시 빈 배열 설정 + setFilteredTransactions([]); } - - console.log('필터링 결과:', filtered.length, '트랜잭션'); - setFilteredTransactions(filtered); }, [transactions, selectedMonth, searchQuery, setFilteredTransactions]); + + // 필터링 트리거 + useEffect(() => { + filterTransactions(); + }, [transactions, selectedMonth, searchQuery, filterTransactions]); + + return { + filterTransactions + }; }; diff --git a/src/hooks/useDataReset.ts b/src/hooks/useDataReset.ts index e18b9e0..c41c9d6 100644 --- a/src/hooks/useDataReset.ts +++ b/src/hooks/useDataReset.ts @@ -5,6 +5,7 @@ import { useToast } from '@/hooks/useToast.wrapper'; import { resetAllStorageData } from '@/utils/storageUtils'; import { clearCloudData } from '@/utils/syncUtils'; import { useAuth } from '@/contexts/auth/AuthProvider'; +import { setSyncEnabled } from '@/utils/sync/syncSettings'; export interface DataResetResult { isCloudResetSuccess: boolean | null; @@ -31,6 +32,8 @@ export const useDataReset = () => { if (cloudResetSuccess) { console.log('클라우드 데이터 초기화 성공'); + // 동기화 비활성화 (중요: 초기화 후 자동 동기화 방지) + setSyncEnabled(false); } else { console.warn('클라우드 데이터 초기화 실패 또는 부분 성공'); } @@ -82,6 +85,12 @@ export const useDataReset = () => { } }); + // 동기화 설정 초기화 + if (user) { + localStorage.removeItem('lastSync'); + localStorage.setItem('syncEnabled', 'false'); + } + // 스토리지 이벤트 트리거하여 다른 컴포넌트에 변경 알림 window.dispatchEvent(new Event('transactionUpdated')); window.dispatchEvent(new Event('budgetDataUpdated')); @@ -93,7 +102,7 @@ export const useDataReset = () => { if (cloudResetSuccess) { toast({ title: "모든 데이터가 초기화되었습니다.", - description: "로컬 및 클라우드의 모든 데이터가 초기화되었습니다.", + description: "로컬 및 클라우드의 모든 데이터가 초기화되었습니다. 동기화 기능이 꺼졌습니다.", }); } else { toast({ diff --git a/src/pages/Transactions.tsx b/src/pages/Transactions.tsx index 49a5a55..975b0af 100644 --- a/src/pages/Transactions.tsx +++ b/src/pages/Transactions.tsx @@ -23,6 +23,7 @@ const Transactions = () => { const { budgetData } = useBudget(); const [isDataLoaded, setIsDataLoaded] = useState(false); + const [isProcessing, setIsProcessing] = useState(false); // 데이터 로드 상태 관리 useEffect(() => { @@ -42,6 +43,28 @@ const Transactions = () => { groupedTransactions[datePart].push(transaction); }); + // 트랜잭션 삭제 핸들러 (예외 처리 개선) + const handleTransactionDelete = (id: string) => { + try { + console.log('Transactions 페이지에서 트랜잭션 삭제:', id); + // 삭제 중임을 표시 + setIsProcessing(true); + + // 1초 후 처리 완료 상태로 변경 (비동기 작업 완료 보장) + setTimeout(() => { + setIsProcessing(false); + }, 1000); + + // 트랜잭션 목록 새로고침 (지연시켜 처리) + setTimeout(() => { + refreshTransactions(); + }, 300); + } catch (error) { + console.error('트랜잭션 삭제 처리 중 오류:', error); + setIsProcessing(false); + } + }; + // 페이지 포커스나 가시성 변경 시 데이터 새로고침 useEffect(() => { const handleVisibilityChange = () => { @@ -65,6 +88,9 @@ const Transactions = () => { }; }, [refreshTransactions]); + // 로딩이나 처리 중이면 비활성화된 UI 상태 표시 + const isDisabled = isLoading || isProcessing; + return (
@@ -81,6 +107,7 @@ const Transactions = () => { className="bg-transparent flex-1 outline-none text-sm" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} + disabled={isDisabled} />
@@ -89,6 +116,7 @@ const Transactions = () => { @@ -101,6 +129,7 @@ const Transactions = () => { @@ -123,16 +152,16 @@ const Transactions = () => {
- {/* Loading State */} - {isLoading && ( + {/* Loading or Processing State */} + {(isLoading || isProcessing) && (
- 로딩 중... + {isProcessing ? '처리 중...' : '로딩 중...'}
)} {/* Empty State */} - {!isLoading && transactions.length === 0 && ( + {!isLoading && !isProcessing && transactions.length === 0 && (

{searchQuery.trim() @@ -143,6 +172,7 @@ const Transactions = () => { @@ -151,9 +181,9 @@ const Transactions = () => { )} {/* Transactions By Date */} - {!isLoading && transactions.length > 0 && ( + {!isLoading && !isProcessing && transactions.length > 0 && (

- {Object.entries(groupedTransactions).map(([date, transactions]) => ( + {Object.entries(groupedTransactions).map(([date, dateTransactions]) => (
@@ -162,10 +192,11 @@ const Transactions = () => {
- {transactions.map(transaction => ( + {dateTransactions.map(transaction => ( ))}
diff --git a/src/utils/sync/clearCloudData.ts b/src/utils/sync/clearCloudData.ts index 735becb..50d718a 100644 --- a/src/utils/sync/clearCloudData.ts +++ b/src/utils/sync/clearCloudData.ts @@ -52,6 +52,10 @@ export const clearCloudData = async (userId: string): Promise => { } catch (e) { console.log('category_budgets 테이블이 없거나 삭제 중 오류 발생:', e); } + + // 동기화 설정 초기화 및 마지막 동기화 시간 초기화 + localStorage.removeItem('lastSync'); + localStorage.setItem('syncEnabled', 'false'); console.log('클라우드 데이터 초기화 완료'); return true;