Fix data loss on sync enable
Addresses an issue where budget data was lost when enabling sync after entering budget information.
This commit is contained in:
@@ -67,12 +67,49 @@ export const useSyncSettings = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 현재 로컬 데이터 백업
|
||||||
|
const budgetDataBackup = localStorage.getItem('budgetData');
|
||||||
|
const categoryBudgetsBackup = localStorage.getItem('categoryBudgets');
|
||||||
|
const transactionsBackup = localStorage.getItem('transactions');
|
||||||
|
|
||||||
|
console.log('동기화 설정 변경 전 로컬 데이터 백업:', {
|
||||||
|
budgetData: budgetDataBackup ? '있음' : '없음',
|
||||||
|
categoryBudgets: categoryBudgetsBackup ? '있음' : '없음',
|
||||||
|
transactions: transactionsBackup ? '있음' : '없음'
|
||||||
|
});
|
||||||
|
|
||||||
setEnabled(checked);
|
setEnabled(checked);
|
||||||
setSyncEnabled(checked);
|
setSyncEnabled(checked);
|
||||||
|
|
||||||
if (checked && user) {
|
if (checked && user) {
|
||||||
|
try {
|
||||||
// 동기화 활성화 시 즉시 동기화 실행
|
// 동기화 활성화 시 즉시 동기화 실행
|
||||||
await performSync();
|
await performSync();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('동기화 중 오류, 로컬 데이터 복원 시도:', error);
|
||||||
|
|
||||||
|
// 오류 발생 시 백업 데이터 복원
|
||||||
|
if (budgetDataBackup) {
|
||||||
|
localStorage.setItem('budgetData', budgetDataBackup);
|
||||||
|
}
|
||||||
|
if (categoryBudgetsBackup) {
|
||||||
|
localStorage.setItem('categoryBudgets', categoryBudgetsBackup);
|
||||||
|
}
|
||||||
|
if (transactionsBackup) {
|
||||||
|
localStorage.setItem('transactions', transactionsBackup);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 이벤트 발생시켜 UI 업데이트
|
||||||
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
|
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||||
|
window.dispatchEvent(new Event('transactionUpdated'));
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "동기화 오류",
|
||||||
|
description: "동기화 중 문제가 발생하여 로컬 데이터가 복원되었습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,23 @@ export const downloadBudgets = async (userId: string): Promise<void> => {
|
|||||||
try {
|
try {
|
||||||
console.log('서버에서 예산 데이터 다운로드 시작');
|
console.log('서버에서 예산 데이터 다운로드 시작');
|
||||||
|
|
||||||
|
// 현재 로컬 예산 데이터 백업
|
||||||
|
const localBudgetData = localStorage.getItem('budgetData');
|
||||||
|
const localCategoryBudgets = localStorage.getItem('categoryBudgets');
|
||||||
|
|
||||||
|
// 서버에 데이터가 없는지 확인
|
||||||
|
const { data: budgetExists, error: checkError } = await supabase
|
||||||
|
.from('budgets')
|
||||||
|
.select('count')
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
// 서버에 데이터가 없고 로컬에 데이터가 있으면 다운로드 건너뜀
|
||||||
|
if ((budgetExists?.count === 0 || !budgetExists) && localBudgetData) {
|
||||||
|
console.log('서버에 예산 데이터가 없고 로컬 데이터가 있어 다운로드 건너뜀');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 예산 데이터 및 카테고리 예산 데이터 가져오기
|
// 예산 데이터 및 카테고리 예산 데이터 가져오기
|
||||||
const [budgetData, categoryData] = await Promise.all([
|
const [budgetData, categoryData] = await Promise.all([
|
||||||
fetchBudgetData(userId),
|
fetchBudgetData(userId),
|
||||||
@@ -19,16 +36,24 @@ export const downloadBudgets = async (userId: string): Promise<void> => {
|
|||||||
|
|
||||||
// 예산 데이터 처리
|
// 예산 데이터 처리
|
||||||
if (budgetData) {
|
if (budgetData) {
|
||||||
await processBudgetData(budgetData);
|
await processBudgetData(budgetData, localBudgetData);
|
||||||
} else {
|
} else {
|
||||||
console.log('서버에서 예산 데이터를 찾을 수 없음');
|
console.log('서버에서 예산 데이터를 찾을 수 없음');
|
||||||
|
// 로컬 데이터가 있으면 유지
|
||||||
|
if (localBudgetData) {
|
||||||
|
console.log('로컬 예산 데이터 유지');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 카테고리 예산 데이터 처리
|
// 카테고리 예산 데이터 처리
|
||||||
if (categoryData && categoryData.length > 0) {
|
if (categoryData && categoryData.length > 0) {
|
||||||
await processCategoryBudgetData(categoryData);
|
await processCategoryBudgetData(categoryData, localCategoryBudgets);
|
||||||
} else {
|
} else {
|
||||||
console.log('서버에서 카테고리 예산 데이터를 찾을 수 없음');
|
console.log('서버에서 카테고리 예산 데이터를 찾을 수 없음');
|
||||||
|
// 로컬 데이터가 있으면 유지
|
||||||
|
if (localCategoryBudgets) {
|
||||||
|
console.log('로컬 카테고리 예산 데이터 유지');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('예산 데이터 다운로드 완료');
|
console.log('예산 데이터 다운로드 완료');
|
||||||
@@ -82,11 +107,16 @@ async function fetchCategoryBudgetData(userId: string) {
|
|||||||
/**
|
/**
|
||||||
* 예산 데이터 처리 및 로컬 저장
|
* 예산 데이터 처리 및 로컬 저장
|
||||||
*/
|
*/
|
||||||
async function processBudgetData(budgetData: any) {
|
async function processBudgetData(budgetData: any, localBudgetDataStr: string | null) {
|
||||||
console.log('서버에서 예산 데이터 수신:', budgetData);
|
console.log('서버에서 예산 데이터 수신:', budgetData);
|
||||||
|
|
||||||
|
// 서버 예산이 0이고 로컬 예산이 있으면 로컬 데이터 유지
|
||||||
|
if (budgetData.total_budget === 0 && localBudgetDataStr) {
|
||||||
|
console.log('서버 예산이 0이고 로컬 예산이 있어 로컬 데이터 유지');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 기존 로컬 데이터 가져오기
|
// 기존 로컬 데이터 가져오기
|
||||||
const localBudgetDataStr = localStorage.getItem('budgetData');
|
|
||||||
let localBudgetData = localBudgetDataStr ? JSON.parse(localBudgetDataStr) : {
|
let localBudgetData = localBudgetDataStr ? JSON.parse(localBudgetDataStr) : {
|
||||||
daily: { targetAmount: 0, spentAmount: 0, remainingAmount: 0 },
|
daily: { targetAmount: 0, spentAmount: 0, remainingAmount: 0 },
|
||||||
weekly: { targetAmount: 0, spentAmount: 0, remainingAmount: 0 },
|
weekly: { targetAmount: 0, spentAmount: 0, remainingAmount: 0 },
|
||||||
@@ -114,6 +144,7 @@ async function processBudgetData(budgetData: any) {
|
|||||||
|
|
||||||
// 로컬 스토리지에 저장
|
// 로컬 스토리지에 저장
|
||||||
localStorage.setItem('budgetData', JSON.stringify(updatedBudgetData));
|
localStorage.setItem('budgetData', JSON.stringify(updatedBudgetData));
|
||||||
|
localStorage.setItem('budgetData_backup', JSON.stringify(updatedBudgetData));
|
||||||
console.log('예산 데이터 로컬 저장 완료', updatedBudgetData);
|
console.log('예산 데이터 로컬 저장 완료', updatedBudgetData);
|
||||||
|
|
||||||
// 이벤트 발생시켜 UI 업데이트
|
// 이벤트 발생시켜 UI 업데이트
|
||||||
@@ -123,9 +154,18 @@ async function processBudgetData(budgetData: any) {
|
|||||||
/**
|
/**
|
||||||
* 카테고리 예산 데이터 처리 및 로컬 저장
|
* 카테고리 예산 데이터 처리 및 로컬 저장
|
||||||
*/
|
*/
|
||||||
async function processCategoryBudgetData(categoryData: any[]) {
|
async function processCategoryBudgetData(categoryData: any[], localCategoryBudgetsStr: string | null) {
|
||||||
console.log(`${categoryData.length}개의 카테고리 예산 수신`);
|
console.log(`${categoryData.length}개의 카테고리 예산 수신`);
|
||||||
|
|
||||||
|
// 서버 카테고리 예산 합계 계산
|
||||||
|
const serverTotal = categoryData.reduce((sum, item) => sum + item.amount, 0);
|
||||||
|
|
||||||
|
// 로컬 카테고리 예산이 있고 서버 데이터가 비어있거나 합계가 0이면 로컬 데이터 유지
|
||||||
|
if (localCategoryBudgetsStr && (categoryData.length === 0 || serverTotal === 0)) {
|
||||||
|
console.log('서버 카테고리 예산이 없거나 0이고 로컬 데이터가 있어 로컬 데이터 유지');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 카테고리 예산 로컬 형식으로 변환
|
// 카테고리 예산 로컬 형식으로 변환
|
||||||
const localCategoryBudgets = categoryData.reduce((acc, curr) => {
|
const localCategoryBudgets = categoryData.reduce((acc, curr) => {
|
||||||
acc[curr.category] = curr.amount;
|
acc[curr.category] = curr.amount;
|
||||||
@@ -134,6 +174,7 @@ async function processCategoryBudgetData(categoryData: any[]) {
|
|||||||
|
|
||||||
// 로컬 스토리지에 저장
|
// 로컬 스토리지에 저장
|
||||||
localStorage.setItem('categoryBudgets', JSON.stringify(localCategoryBudgets));
|
localStorage.setItem('categoryBudgets', JSON.stringify(localCategoryBudgets));
|
||||||
|
localStorage.setItem('categoryBudgets_backup', JSON.stringify(localCategoryBudgets));
|
||||||
console.log('카테고리 예산 로컬 저장 완료', localCategoryBudgets);
|
console.log('카테고리 예산 로컬 저장 완료', localCategoryBudgets);
|
||||||
|
|
||||||
// 이벤트 발생시켜 UI 업데이트
|
// 이벤트 발생시켜 UI 업데이트
|
||||||
|
|||||||
@@ -1,71 +1,193 @@
|
|||||||
|
|
||||||
import { supabase } from '@/lib/supabase';
|
import { supabase } from '@/lib/supabase';
|
||||||
|
import { uploadBudgets, downloadBudgets } from './budget';
|
||||||
|
import { uploadTransactions, downloadTransactions } from './transaction';
|
||||||
import { setLastSyncTime } from './time';
|
import { setLastSyncTime } from './time';
|
||||||
|
|
||||||
|
export interface SyncResult {
|
||||||
|
success: boolean;
|
||||||
|
partial: boolean;
|
||||||
|
uploadSuccess: boolean;
|
||||||
|
downloadSuccess: boolean;
|
||||||
|
details?: {
|
||||||
|
budgetUpload?: boolean;
|
||||||
|
budgetDownload?: boolean;
|
||||||
|
transactionUpload?: boolean;
|
||||||
|
transactionDownload?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 모든 데이터 동기화 기능
|
* 모든 데이터를 동기화합니다 (업로드 우선 수행)
|
||||||
*/
|
*/
|
||||||
export const syncAllData = async (userId: string): Promise<void> => {
|
export const syncAllData = async (userId: string): Promise<SyncResult> => {
|
||||||
if (!userId) {
|
// 로컬 데이터 백업
|
||||||
throw new Error('사용자 ID가 필요합니다');
|
const backupBudgetData = localStorage.getItem('budgetData');
|
||||||
}
|
const backupCategoryBudgets = localStorage.getItem('categoryBudgets');
|
||||||
|
const backupTransactions = localStorage.getItem('transactions');
|
||||||
|
|
||||||
try {
|
const result: SyncResult = {
|
||||||
// 로컬 트랜잭션 데이터 가져오기
|
success: false,
|
||||||
const transactionsJSON = localStorage.getItem('transactions');
|
partial: false,
|
||||||
const transactions = transactionsJSON ? JSON.parse(transactionsJSON) : [];
|
uploadSuccess: false,
|
||||||
|
downloadSuccess: false,
|
||||||
// 예산 데이터 가져오기
|
details: {
|
||||||
const budgetDataJSON = localStorage.getItem('budgetData');
|
budgetUpload: false,
|
||||||
const budgetData = budgetDataJSON ? JSON.parse(budgetDataJSON) : {};
|
budgetDownload: false,
|
||||||
|
transactionUpload: false,
|
||||||
// 카테고리 예산 가져오기
|
transactionDownload: false
|
||||||
const categoryBudgetsJSON = localStorage.getItem('categoryBudgets');
|
|
||||||
const categoryBudgets = categoryBudgetsJSON ? JSON.parse(categoryBudgetsJSON) : {};
|
|
||||||
|
|
||||||
// 트랜잭션 데이터 동기화
|
|
||||||
for (const transaction of transactions) {
|
|
||||||
// 이미 동기화된 데이터인지 확인 (transaction_id로 확인)
|
|
||||||
const { data: existingData } = await supabase
|
|
||||||
.from('transactions')
|
|
||||||
.select('*')
|
|
||||||
.eq('transaction_id', transaction.id)
|
|
||||||
.eq('user_id', userId);
|
|
||||||
|
|
||||||
// 존재하지 않는 경우에만 삽입
|
|
||||||
if (!existingData || existingData.length === 0) {
|
|
||||||
await supabase.from('transactions').insert({
|
|
||||||
user_id: userId,
|
|
||||||
title: transaction.title,
|
|
||||||
amount: transaction.amount,
|
|
||||||
date: transaction.date,
|
|
||||||
category: transaction.category,
|
|
||||||
type: transaction.type,
|
|
||||||
transaction_id: transaction.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 예산 데이터 동기화
|
|
||||||
await supabase.from('budget_data').upsert({
|
|
||||||
user_id: userId,
|
|
||||||
data: budgetData,
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
// 카테고리 예산 동기화
|
|
||||||
await supabase.from('category_budgets').upsert({
|
|
||||||
user_id: userId,
|
|
||||||
data: categoryBudgets,
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
// 마지막 동기화 시간 업데이트
|
|
||||||
setLastSyncTime();
|
|
||||||
|
|
||||||
console.log('모든 데이터가 성공적으로 동기화되었습니다');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('데이터 동기화 중 오류 발생:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('데이터 동기화 시작 - 사용자 ID:', userId);
|
||||||
|
|
||||||
|
// 여기서는 업로드를 먼저 시도합니다 (로컬 데이터 보존을 위해)
|
||||||
|
try {
|
||||||
|
// 예산 데이터 업로드
|
||||||
|
await uploadBudgets(userId);
|
||||||
|
result.details!.budgetUpload = true;
|
||||||
|
console.log('예산 업로드 성공');
|
||||||
|
|
||||||
|
// 트랜잭션 데이터 업로드
|
||||||
|
await uploadTransactions(userId);
|
||||||
|
result.details!.transactionUpload = true;
|
||||||
|
console.log('트랜잭션 업로드 성공');
|
||||||
|
|
||||||
|
// 업로드 성공 설정
|
||||||
|
result.uploadSuccess = true;
|
||||||
|
} catch (uploadError) {
|
||||||
|
console.error('데이터 업로드 실패:', uploadError);
|
||||||
|
result.uploadSuccess = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 그 다음 다운로드 시도
|
||||||
|
try {
|
||||||
|
// 서버에 데이터가 없는 경우를 확인하기 위해 먼저 데이터 유무 검사
|
||||||
|
const { data: budgetData } = await supabase
|
||||||
|
.from('budgets')
|
||||||
|
.select('count')
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
const { data: transactionsData } = await supabase
|
||||||
|
.from('transactions')
|
||||||
|
.select('count')
|
||||||
|
.eq('user_id', userId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
// 서버에 데이터가 없지만 로컬에 데이터가 있는 경우, 다운로드를 건너뜀
|
||||||
|
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 {
|
||||||
|
// 예산 데이터 다운로드
|
||||||
|
await downloadBudgets(userId);
|
||||||
|
result.details!.budgetDownload = true;
|
||||||
|
console.log('예산 다운로드 성공');
|
||||||
|
|
||||||
|
// 트랜잭션 데이터 다운로드
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI 업데이트
|
||||||
|
window.dispatchEvent(new Event('budgetDataUpdated'));
|
||||||
|
window.dispatchEvent(new Event('categoryBudgetsUpdated'));
|
||||||
|
window.dispatchEvent(new Event('transactionUpdated'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 부분 성공 여부 설정
|
||||||
|
result.partial = (result.uploadSuccess || result.downloadSuccess) && !(result.uploadSuccess && result.downloadSuccess);
|
||||||
|
|
||||||
|
// 전체 성공 여부 설정
|
||||||
|
result.success = result.uploadSuccess || result.downloadSuccess;
|
||||||
|
|
||||||
|
// 동기화 시간 기록
|
||||||
|
if (result.success) {
|
||||||
|
setLastSyncTime(new Date().toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('데이터 동기화 결과:', result);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('데이터 동기화 중 치명적 오류:', error);
|
||||||
|
|
||||||
|
// 백업 데이터 복원
|
||||||
|
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'));
|
||||||
|
|
||||||
|
result.success = false;
|
||||||
|
result.partial = false;
|
||||||
|
result.uploadSuccess = false;
|
||||||
|
result.downloadSuccess = false;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 서버에 대한 안전한 동기화 래퍼
|
||||||
|
* 오류 처리와 재시도 로직을 포함
|
||||||
|
*/
|
||||||
|
export const trySyncAllData = async (userId: string): Promise<SyncResult> => {
|
||||||
|
console.log('안전한 데이터 동기화 시도');
|
||||||
|
let attempts = 0;
|
||||||
|
|
||||||
|
const trySync = async (): Promise<SyncResult> => {
|
||||||
|
try {
|
||||||
|
return await syncAllData(userId);
|
||||||
|
} catch (error) {
|
||||||
|
attempts++;
|
||||||
|
console.error(`동기화 시도 ${attempts} 실패:`, error);
|
||||||
|
|
||||||
|
if (attempts < 2) {
|
||||||
|
console.log('동기화 재시도 중...');
|
||||||
|
return trySync();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
partial: false,
|
||||||
|
uploadSuccess: false,
|
||||||
|
downloadSuccess: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return trySync();
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user