From befb29611bbc4981476c84ab38ce9830cb0f4ae7 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 21 Mar 2025 12:29:28 +0000 Subject: [PATCH] Fix data loss on sync after reset Addresses an issue where budget data was lost after a data reset, logout, and subsequent synchronization. The fix ensures budget data is correctly restored in such scenarios. --- src/hooks/sync/syncResultHandler.ts | 62 +++++---- src/utils/sync/budget/uploadBudget.ts | 102 +++++++++++--- src/utils/sync/data.ts | 188 +++++++++++++++++--------- src/utils/sync/downloadBudget.ts | 89 ++++++++---- 4 files changed, 303 insertions(+), 138 deletions(-) diff --git a/src/hooks/sync/syncResultHandler.ts b/src/hooks/sync/syncResultHandler.ts index 502e242..e8f1a2b 100644 --- a/src/hooks/sync/syncResultHandler.ts +++ b/src/hooks/sync/syncResultHandler.ts @@ -1,47 +1,49 @@ +import { SyncResult } from '@/utils/sync/data'; import { toast } from '@/hooks/useToast.wrapper'; -import type { SyncResult } from '@/utils/syncUtils'; -/** - * 동기화 결과 처리 함수 - */ +// 동기화 결과 처리 함수 export const handleSyncResult = (result: SyncResult) => { - if (!result) { - toast({ - title: "동기화 오류", - description: "동기화 결과를 처리할 수 없습니다.", - variant: "destructive" - }); - return; - } - if (result.success) { - if (result.partial) { - // 부분 성공 처리 - toast({ - title: "부분 동기화 완료", - description: "일부 데이터만 동기화되었습니다. 다시 시도해보세요.", - variant: "default" - }); - } else { - // 전체 성공 처리 + if (result.uploadSuccess && result.downloadSuccess) { + // 양방향 동기화 성공 toast({ title: "동기화 완료", description: "모든 데이터가 성공적으로 동기화되었습니다.", - variant: "default" }); - - // 데이터 변경 이벤트 발생 - window.dispatchEvent(new Event('budgetDataUpdated')); - window.dispatchEvent(new Event('categoryBudgetsUpdated')); - window.dispatchEvent(new Event('transactionUpdated')); + } else if (result.uploadSuccess) { + // 업로드만 성공 + toast({ + title: "동기화 완료", + description: "로컬 데이터가 클라우드에 업로드되었습니다.", + }); + } else if (result.downloadSuccess) { + // 다운로드만 성공 + toast({ + title: "동기화 완료", + description: "클라우드 데이터가 기기에 다운로드되었습니다.", + }); } + + // 상세 결과 로깅 + console.log("동기화 세부 결과:", { + 예산업로드: result.details?.budgetUpload ? '성공' : '실패', + 예산다운로드: result.details?.budgetDownload ? '성공' : '실패', + 트랜잭션업로드: result.details?.transactionUpload ? '성공' : '실패', + 트랜잭션다운로드: result.details?.transactionDownload ? '성공' : '실패' + }); + + return true; } else { - // 실패 처리 + // 동기화 실패 + console.error("동기화 실패 세부 결과:", result.details); + toast({ title: "동기화 실패", - description: "데이터 동기화에 실패했습니다. 나중에 다시 시도해보세요.", + description: "데이터 동기화 중 문제가 발생했습니다. 다시 시도해주세요.", variant: "destructive" }); + + return false; } }; diff --git a/src/utils/sync/budget/uploadBudget.ts b/src/utils/sync/budget/uploadBudget.ts index e262bd1..a2caf81 100644 --- a/src/utils/sync/budget/uploadBudget.ts +++ b/src/utils/sync/budget/uploadBudget.ts @@ -1,3 +1,4 @@ + import { supabase } from '@/lib/supabase'; import { isSyncEnabled } from '../syncSettings'; import { @@ -20,10 +21,16 @@ export const uploadBudgets = async (userId: string): Promise => { // 예산 데이터 업로드 if (budgetDataStr) { const budgetData = JSON.parse(budgetDataStr); - await uploadBudgetData(userId, budgetData); - // 업로드 성공 후 수정 추적 정보 초기화 - clearModifiedBudget(); + // 월간 예산이 0보다 클 때만 업로드 + if (budgetData.monthly && budgetData.monthly.targetAmount > 0) { + console.log('유효한 월간 예산 발견:', budgetData.monthly.targetAmount); + await uploadBudgetData(userId, budgetData); + // 업로드 성공 후 수정 추적 정보 초기화 + clearModifiedBudget(); + } else { + console.log('월간 예산이 0 이하거나 없어서 업로드 건너뜀'); + } } else { console.log('업로드할 예산 데이터가 없음'); } @@ -31,10 +38,17 @@ export const uploadBudgets = async (userId: string): Promise => { // 카테고리 예산 업로드 if (categoryBudgetsStr) { const categoryBudgets = JSON.parse(categoryBudgetsStr); - await uploadCategoryBudgets(userId, categoryBudgets); - // 업로드 성공 후 수정 추적 정보 초기화 - clearModifiedCategoryBudgets(); + // 총 카테고리 예산이 0보다 클 때만 업로드 + const totalCategoryBudget = Object.values(categoryBudgets).reduce((sum: number, val: number) => sum + val, 0); + if (totalCategoryBudget > 0) { + console.log('유효한 카테고리 예산 발견:', totalCategoryBudget); + await uploadCategoryBudgets(userId, categoryBudgets); + // 업로드 성공 후 수정 추적 정보 초기화 + clearModifiedCategoryBudgets(); + } else { + console.log('카테고리 예산이 모두 0이어서 업로드 건너뜀'); + } } else { console.log('업로드할 카테고리 예산이 없음'); } @@ -73,28 +87,42 @@ async function uploadBudgetData(userId: string, parsedBudgetData: Record 0) { - // 기존 데이터 업데이트 - const { error } = await supabase - .from('budgets') - .update({ - total_budget: monthlyTarget, - updated_at: currentTimestamp - }) - .eq('id', existingBudgets[0].id); + const existingBudget = existingBudgets[0]; + // 새 예산이 기존 예산보다 클 때만 업데이트 + if (monthlyTarget > existingBudget.total_budget) { + console.log(`새 예산(${monthlyTarget})이 기존 예산(${existingBudget.total_budget})보다 큼, 업데이트 실행`); - if (error) { - console.error('예산 데이터 업데이트 실패:', error); - throw error; + // 기존 데이터 업데이트 + const { error } = await supabase + .from('budgets') + .update({ + total_budget: monthlyTarget, + updated_at: currentTimestamp + }) + .eq('id', existingBudget.id); + + if (error) { + console.error('예산 데이터 업데이트 실패:', error); + throw error; + } + + console.log('예산 데이터 업데이트 성공'); + } else { + console.log(`새 예산(${monthlyTarget})이 기존 예산(${existingBudget.total_budget})보다 작거나 같음, 업데이트 건너뜀`); } - - console.log('예산 데이터 업데이트 성공'); } else { // 새 데이터 삽입 const { error } = await supabase @@ -123,6 +151,40 @@ async function uploadBudgetData(userId: string, parsedBudgetData: Record): Promise { console.log('카테고리 예산 업로드:', parsedCategoryBudgets); + // 기존 카테고리 예산 확인 + const { data: existingCategoryBudgets, error: fetchError } = await supabase + .from('category_budgets') + .select('*') + .eq('user_id', userId); + + if (fetchError) { + console.error('기존 카테고리 예산 조회 실패:', fetchError); + throw fetchError; + } + + // 기존 카테고리 예산의 총액 계산 + let existingTotal = 0; + if (existingCategoryBudgets && existingCategoryBudgets.length > 0) { + existingTotal = existingCategoryBudgets.reduce((sum, item) => sum + item.amount, 0); + } + + // 새 카테고리 예산의 총액 계산 + const newTotal = Object.values(parsedCategoryBudgets).reduce((sum: number, val: number) => sum + val, 0); + + // 새 카테고리 예산 총액이 기존 카테고리 예산 총액보다 작거나 같으면 업로드 건너뜀 + if (newTotal <= existingTotal && existingTotal > 0) { + console.log(`새 카테고리 예산 총액(${newTotal})이 기존 예산 총액(${existingTotal})보다 작거나 같음, 업로드 건너뜀`); + return; + } + + // 새 카테고리 예산 총액이 0이면 업로드 건너뜀 + if (newTotal <= 0) { + console.log('새 카테고리 예산 총액이 0이하여서 업로드 건너뜀'); + return; + } + + console.log(`새 카테고리 예산 총액(${newTotal})이 기존 예산 총액(${existingTotal})보다 큼, 업로드 실행`); + // 기존 카테고리 예산 삭제 const { error: deleteError } = await supabase .from('category_budgets') diff --git a/src/utils/sync/data.ts b/src/utils/sync/data.ts index a431107..7246a9f 100644 --- a/src/utils/sync/data.ts +++ b/src/utils/sync/data.ts @@ -40,81 +40,143 @@ export const syncAllData = async (userId: string): Promise => { try { console.log('데이터 동기화 시작 - 사용자 ID:', userId); - // 여기서는 업로드를 먼저 시도합니다 (로컬 데이터 보존을 위해) - try { - // 예산 데이터 업로드 - await uploadBudgets(userId); - result.details!.budgetUpload = true; - console.log('예산 업로드 성공'); + // 서버에 데이터가 있는지 먼저 확인 + const { data: budgetData } = await supabase + .from('budgets') + .select('count') + .eq('user_id', userId) + .single(); - // 트랜잭션 데이터 업로드 - await uploadTransactions(userId); - result.details!.transactionUpload = true; - console.log('트랜잭션 업로드 성공'); - - // 업로드 성공 설정 - result.uploadSuccess = true; - } catch (uploadError) { - console.error('데이터 업로드 실패:', uploadError); - result.uploadSuccess = false; - } + const { data: transactionsData } = await supabase + .from('transactions') + .select('count') + .eq('user_id', userId) + .single(); - // 그 다음 다운로드 시도 - try { - // 서버에 데이터가 없는 경우를 확인하기 위해 먼저 데이터 유무 검사 - const { data: budgetData } = await supabase - .from('budgets') - .select('count') - .eq('user_id', userId) - .single(); + const serverHasBudgetData = (budgetData?.count || 0) > 0; + const serverHasTransactionData = (transactionsData?.count || 0) > 0; + const serverHasData = serverHasBudgetData || serverHasTransactionData; + + // 로컬 데이터 존재 여부 확인 + const localHasBudgetData = backupBudgetData && JSON.parse(backupBudgetData).monthly.targetAmount > 0; + const localHasTransactionData = backupTransactions && JSON.parse(backupTransactions).length > 0; + const localHasData = localHasBudgetData || localHasTransactionData; + + console.log('데이터 존재 여부:', { + 서버: { + 예산: serverHasBudgetData, + 거래: serverHasTransactionData + }, + 로컬: { + 예산: localHasBudgetData, + 거래: localHasTransactionData + } + }); + + // 서버에 데이터가 없고 로컬에 데이터가 있으면 업로드 먼저 + if (!serverHasData && localHasData) { + console.log('서버에 데이터가 없고 로컬에 데이터가 있어 업로드 우선 실행'); + + try { + // 예산 데이터 업로드 + if (localHasBudgetData) { + await uploadBudgets(userId); + result.details!.budgetUpload = true; + console.log('예산 업로드 성공'); + } - const { data: transactionsData } = await supabase - .from('transactions') - .select('count') - .eq('user_id', userId) - .single(); + // 트랜잭션 데이터 업로드 + if (localHasTransactionData) { + await uploadTransactions(userId); + result.details!.transactionUpload = true; + console.log('트랜잭션 업로드 성공'); + } + + // 업로드 성공 설정 + result.uploadSuccess = true; + } catch (uploadError) { + console.error('데이터 업로드 실패:', uploadError); + result.uploadSuccess = false; + } + } + // 서버에 데이터가 있으면 다운로드 먼저 (기존 로직) + else if (serverHasData) { + console.log('서버에 데이터가 있어 다운로드 우선 실행'); - // 서버에 데이터가 없지만 로컬에 데이터가 있는 경우, 다운로드를 건너뜀 - const serverHasData = (budgetData?.count || 0) > 0 || (transactionsData?.count || 0) > 0; - - if (!serverHasData && (backupBudgetData || backupTransactions)) { - console.log('서버에 데이터가 없고 로컬 데이터가 있어 다운로드 건너뜀'); - result.downloadSuccess = true; - result.details!.budgetDownload = true; - result.details!.transactionDownload = true; - } else { + try { // 예산 데이터 다운로드 - await downloadBudgets(userId); - result.details!.budgetDownload = true; - console.log('예산 다운로드 성공'); + if (serverHasBudgetData) { + await downloadBudgets(userId); + result.details!.budgetDownload = true; + console.log('예산 다운로드 성공'); + } // 트랜잭션 데이터 다운로드 - await downloadTransactions(userId); - result.details!.transactionDownload = true; - console.log('트랜잭션 다운로드 성공'); + if (serverHasTransactionData) { + await downloadTransactions(userId); + result.details!.transactionDownload = true; + console.log('트랜잭션 다운로드 성공'); + } // 다운로드 성공 설정 result.downloadSuccess = true; - } - } catch (downloadError) { - console.error('데이터 다운로드 실패:', downloadError); - result.downloadSuccess = false; - - // 다운로드 실패 시 로컬 데이터 복원 - if (backupBudgetData) { - localStorage.setItem('budgetData', backupBudgetData); - } - if (backupCategoryBudgets) { - localStorage.setItem('categoryBudgets', backupCategoryBudgets); - } - if (backupTransactions) { - localStorage.setItem('transactions', backupTransactions); + } catch (downloadError) { + console.error('데이터 다운로드 실패:', downloadError); + result.downloadSuccess = false; + + // 다운로드 실패 시 로컬 데이터 복원 + if (backupBudgetData) { + localStorage.setItem('budgetData', backupBudgetData); + } + if (backupCategoryBudgets) { + localStorage.setItem('categoryBudgets', backupCategoryBudgets); + } + if (backupTransactions) { + localStorage.setItem('transactions', backupTransactions); + } + + // UI 업데이트 + window.dispatchEvent(new Event('budgetDataUpdated')); + window.dispatchEvent(new Event('categoryBudgetsUpdated')); + window.dispatchEvent(new Event('transactionUpdated')); } - // UI 업데이트 - window.dispatchEvent(new Event('budgetDataUpdated')); - window.dispatchEvent(new Event('categoryBudgetsUpdated')); - window.dispatchEvent(new Event('transactionUpdated')); + // 다운로드 후 로컬 데이터 있으면 추가 업로드 시도 + if (result.downloadSuccess && localHasData) { + console.log('다운로드 후 로컬 데이터 업로드 시도'); + + try { + // 예산 데이터 업로드 + if (localHasBudgetData) { + await uploadBudgets(userId); + result.details!.budgetUpload = true; + console.log('예산 업로드 성공'); + } + + // 트랜잭션 데이터 업로드 + if (localHasTransactionData) { + await uploadTransactions(userId); + result.details!.transactionUpload = true; + console.log('트랜잭션 업로드 성공'); + } + + // 업로드 성공 설정 + result.uploadSuccess = true; + } catch (uploadError) { + console.error('데이터 업로드 실패:', uploadError); + result.uploadSuccess = false; + } + } + } + // 양쪽 다 데이터가 없는 경우 + else { + console.log('서버와 로컬 모두 데이터가 없어 동기화 건너뜀'); + result.uploadSuccess = true; + result.downloadSuccess = true; + result.details!.budgetUpload = true; + result.details!.budgetDownload = true; + result.details!.transactionUpload = true; + result.details!.transactionDownload = true; } // 부분 성공 여부 설정 diff --git a/src/utils/sync/downloadBudget.ts b/src/utils/sync/downloadBudget.ts index 21f67c4..d53c273 100644 --- a/src/utils/sync/downloadBudget.ts +++ b/src/utils/sync/downloadBudget.ts @@ -35,6 +35,12 @@ export const downloadBudgets = async (userId: string): Promise => { if (budgetData && budgetData.length > 0) { const monthlyBudget = budgetData[0].total_budget; + // 예산이 0이거나 음수면 처리하지 않음 + if (monthlyBudget <= 0) { + console.log('서버에 저장된 예산이 0이하여서 다운로드 건너뜀:', monthlyBudget); + return; + } + // 기존 예산 데이터 가져오기 let budgetDataObj = { daily: { targetAmount: 0, spentAmount: 0, remainingAmount: 0 }, @@ -61,26 +67,35 @@ export const downloadBudgets = async (userId: string): Promise => { console.error('로컬 예산 데이터 파싱 오류:', e); } - // 월간 예산만 업데이트 - budgetDataObj.monthly.targetAmount = monthlyBudget; - - // 일간, 주간 예산 계산 (이전 비율 유지) - const daysInMonth = new Date(currentYear, currentMonth, 0).getDate(); - budgetDataObj.daily = { - targetAmount: Math.round(monthlyBudget / daysInMonth), - spentAmount: budgetDataObj.daily?.spentAmount || 0, - remainingAmount: Math.round(monthlyBudget / daysInMonth) - (budgetDataObj.daily?.spentAmount || 0) - }; - - budgetDataObj.weekly = { - targetAmount: Math.round(monthlyBudget * 7 / daysInMonth), - spentAmount: budgetDataObj.weekly?.spentAmount || 0, - remainingAmount: Math.round(monthlyBudget * 7 / daysInMonth) - (budgetDataObj.weekly?.spentAmount || 0) - }; - - // 업데이트된 예산 데이터 저장 - localStorage.setItem('budgetData', JSON.stringify(budgetDataObj)); - console.log('월간 예산 다운로드 및 저장 완료:', monthlyBudget); + // 서버의 예산이 로컬 예산보다 클 때에만 로컬 예산 덮어쓰기 + if (monthlyBudget > budgetDataObj.monthly.targetAmount) { + console.log('서버 예산이 로컬 예산보다 큼. 로컬 예산 업데이트:', + `서버: ${monthlyBudget}, 로컬: ${budgetDataObj.monthly.targetAmount}`); + + // 월간 예산만 업데이트 + budgetDataObj.monthly.targetAmount = monthlyBudget; + + // 일간, 주간 예산 계산 (이전 비율 유지) + const daysInMonth = new Date(currentYear, currentMonth, 0).getDate(); + budgetDataObj.daily = { + targetAmount: Math.round(monthlyBudget / daysInMonth), + spentAmount: budgetDataObj.daily?.spentAmount || 0, + remainingAmount: Math.round(monthlyBudget / daysInMonth) - (budgetDataObj.daily?.spentAmount || 0) + }; + + budgetDataObj.weekly = { + targetAmount: Math.round(monthlyBudget * 7 / daysInMonth), + spentAmount: budgetDataObj.weekly?.spentAmount || 0, + remainingAmount: Math.round(monthlyBudget * 7 / daysInMonth) - (budgetDataObj.weekly?.spentAmount || 0) + }; + + // 업데이트된 예산 데이터 저장 + localStorage.setItem('budgetData', JSON.stringify(budgetDataObj)); + console.log('월간 예산 다운로드 및 저장 완료:', monthlyBudget); + } else { + console.log('로컬 예산이 서버 예산보다 크거나 같음. 예산 유지:', + `로컬: ${budgetDataObj.monthly.targetAmount}, 서버: ${monthlyBudget}`); + } } else { console.log('서버에 저장된 월간 예산 데이터가 없습니다.'); } @@ -97,16 +112,40 @@ export const downloadBudgets = async (userId: string): Promise => { } if (categoryData && categoryData.length > 0) { + // 현재 로컬 카테고리 예산 확인 + let currentCategoryBudgets: Record = {}; + try { + const storedCategoryBudgets = localStorage.getItem('categoryBudgets'); + if (storedCategoryBudgets) { + currentCategoryBudgets = JSON.parse(storedCategoryBudgets); + } + } catch (e) { + console.error('로컬 카테고리 예산 파싱 오류:', e); + } + // 카테고리 예산 객체 구성 - const categoryBudgets: Record = {}; + const serverCategoryBudgets: Record = {}; categoryData.forEach(item => { - categoryBudgets[item.category] = item.amount; + serverCategoryBudgets[item.category] = item.amount; }); - // 카테고리 예산 저장 - localStorage.setItem('categoryBudgets', JSON.stringify(categoryBudgets)); - console.log('카테고리 예산 다운로드 및 저장 완료:', categoryBudgets); + // 서버 카테고리 예산 총액 계산 + const serverTotal = Object.values(serverCategoryBudgets).reduce((sum, val) => sum + val, 0); + const localTotal = Object.values(currentCategoryBudgets).reduce((sum, val) => sum + val, 0); + + // 서버 카테고리 예산이 로컬보다 클 때만 업데이트 + if (serverTotal > localTotal) { + console.log('서버 카테고리 예산이 로컬보다 큼. 로컬 업데이트:', + `서버: ${serverTotal}, 로컬: ${localTotal}`); + + // 카테고리 예산 저장 + localStorage.setItem('categoryBudgets', JSON.stringify(serverCategoryBudgets)); + console.log('카테고리 예산 다운로드 및 저장 완료:', serverCategoryBudgets); + } else { + console.log('로컬 카테고리 예산이 서버보다 크거나 같음. 유지:', + `로컬: ${localTotal}, 서버: ${serverTotal}`); + } } else { console.log('서버에 저장된 카테고리 예산 데이터가 없습니다.'); }