/** * 동기화 관련 React Query 훅들 * * 기존 동기화 로직을 React Query로 전환하여 * 백그라운드 동기화와 상태 관리를 최적화합니다. */ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { trySyncAllData, getLastSyncTime, setLastSyncTime, } from "@/utils/syncUtils"; import { queryKeys, queryConfigs, handleQueryError, invalidateQueries, } from "@/lib/query/queryClient"; import { syncLogger } from "@/utils/logger"; import { useAuthStore } from "@/stores"; import { toast } from "@/hooks/useToast.wrapper"; import useNotifications from "@/hooks/useNotifications"; /** * 마지막 동기화 시간 조회 쿼리 */ export const useLastSyncTimeQuery = () => { return useQuery({ queryKey: queryKeys.sync.lastSync(), queryFn: async () => { const lastSyncTime = getLastSyncTime(); syncLogger.info("마지막 동기화 시간 조회", { lastSyncTime }); return lastSyncTime; }, staleTime: 30 * 1000, // 30초 gcTime: 5 * 60 * 1000, // 5분 }); }; /** * 동기화 상태 조회 쿼리 */ export const useSyncStatusQuery = () => { const { user } = useAuthStore(); return useQuery({ queryKey: queryKeys.sync.status(), queryFn: async () => { if (!user?.id) { return { canSync: false, reason: "사용자 인증이 필요합니다.", lastSyncTime: null, }; } const lastSyncTime = getLastSyncTime(); const now = new Date(); const lastSync = lastSyncTime ? new Date(lastSyncTime) : null; // 마지막 동기화로부터 얼마나 시간이 지났는지 계산 const timeSinceLastSync = lastSync ? Math.floor((now.getTime() - lastSync.getTime()) / 1000 / 60) // 분 단위 : null; return { canSync: true, reason: null, lastSyncTime, timeSinceLastSync, needsSync: !lastSync || timeSinceLastSync > 30, // 30분 이상 지났으면 동기화 필요 }; }, ...queryConfigs.userInfo, enabled: !!user?.id, }); }; /** * 수동 동기화 뮤테이션 * * - 사용자가 수동으로 동기화를 트리거할 때 사용 * - 성공 시 모든 관련 쿼리 무효화 * - 알림 및 토스트 메시지 제공 */ export const useManualSyncMutation = () => { const queryClient = useQueryClient(); const { user } = useAuthStore(); const { addNotification } = useNotifications(); return useMutation({ mutationFn: async (): Promise => { if (!user?.id) { throw new Error("사용자 인증이 필요합니다."); } syncLogger.info("수동 동기화 뮤테이션 시작", { userId: user.id }); // 동기화 실행 const result = await trySyncAllData(user.id); if (!result.success) { throw new Error(result.error || "동기화에 실패했습니다."); } // 동기화 시간 업데이트 const currentTime = new Date().toISOString(); setLastSyncTime(currentTime); syncLogger.info("수동 동기화 성공", { syncTime: currentTime, result, }); return { ...result, syncTime: currentTime }; }, // 뮤테이션 시작 시 onMutate: () => { syncLogger.info("동기화 시작 알림"); addNotification("동기화 시작", "데이터 동기화가 시작되었습니다."); }, // 성공 시 처리 onSuccess: (result) => { // 모든 쿼리 무효화하여 최신 데이터 로드 invalidateQueries.all(); // 동기화 관련 쿼리 즉시 업데이트 queryClient.setQueryData(queryKeys.sync.lastSync(), result.syncTime); // 성공 알림 toast({ title: "동기화 완료", description: "모든 데이터가 성공적으로 동기화되었습니다.", }); addNotification( "동기화 완료", "모든 데이터가 성공적으로 동기화되었습니다." ); syncLogger.info("수동 동기화 뮤테이션 성공 완료", result); }, // 에러 시 처리 onError: (error: any) => { const friendlyMessage = handleQueryError(error, "동기화"); syncLogger.error("수동 동기화 뮤테이션 실패:", friendlyMessage); toast({ title: "동기화 실패", description: friendlyMessage, variant: "destructive", }); addNotification("동기화 실패", friendlyMessage); }, }); }; /** * 백그라운드 자동 동기화 뮤테이션 * * - 조용한 동기화 (알림 없음) * - 에러 시에도 사용자를 방해하지 않음 * - 성공 시에만 데이터 업데이트 */ export const useBackgroundSyncMutation = () => { const queryClient = useQueryClient(); const { user } = useAuthStore(); return useMutation({ mutationFn: async (): Promise => { if (!user?.id) { throw new Error("사용자 인증이 필요합니다."); } syncLogger.info("백그라운드 동기화 시작", { userId: user.id }); const result = await trySyncAllData(user.id); if (!result.success) { throw new Error(result.error || "백그라운드 동기화에 실패했습니다."); } const currentTime = new Date().toISOString(); setLastSyncTime(currentTime); syncLogger.info("백그라운드 동기화 성공", { syncTime: currentTime, }); return { ...result, syncTime: currentTime }; }, // 성공 시 조용히 데이터 업데이트 onSuccess: (result) => { // 트랜잭션과 예산 데이터만 조용히 업데이트 invalidateQueries.transactions(); invalidateQueries.budget(); // 동기화 시간 업데이트 queryClient.setQueryData(queryKeys.sync.lastSync(), result.syncTime); syncLogger.info("백그라운드 동기화 완료 - 데이터 업데이트됨"); }, // 에러 시 조용히 로그만 남김 onError: (error: any) => { syncLogger.warn( "백그라운드 동기화 실패 (조용히 처리됨):", error?.message ); }, }); }; /** * 자동 동기화 간격 설정을 위한 쿼리 * * - 설정된 간격에 따라 백그라운드 동기화 실행 * - 네트워크 상태에 따른 동적 조정 */ export const useAutoSyncQuery = (intervalMinutes = 5) => { const { user } = useAuthStore(); const backgroundSyncMutation = useBackgroundSyncMutation(); return useQuery({ queryKey: ["auto-sync", intervalMinutes], queryFn: async () => { if (!user?.id) { return null; } // 백그라운드 동기화 실행 if (!backgroundSyncMutation.isPending) { backgroundSyncMutation.mutate(); } return new Date().toISOString(); }, enabled: !!user?.id, refetchInterval: intervalMinutes * 60 * 1000, // 분을 밀리초로 변환 refetchIntervalInBackground: false, // 백그라운드에서는 실행하지 않음 staleTime: Infinity, // 항상 최신으로 유지 gcTime: 0, // 캐시하지 않음 }); }; /** * 통합 동기화 훅 (기존 useManualSync와 호환성 유지) * * React Query 뮤테이션과 기존 인터페이스를 결합 */ export const useSync = () => { const { user } = useAuthStore(); const lastSyncQuery = useLastSyncTimeQuery(); const syncStatusQuery = useSyncStatusQuery(); const manualSyncMutation = useManualSyncMutation(); const backgroundSyncMutation = useBackgroundSyncMutation(); return { // 동기화 상태 lastSyncTime: lastSyncQuery.data, syncStatus: syncStatusQuery.data, // 수동 동기화 (기존 handleManualSync와 동일한 인터페이스) syncing: manualSyncMutation.isPending, handleManualSync: manualSyncMutation.mutate, // 백그라운드 동기화 backgroundSyncing: backgroundSyncMutation.isPending, triggerBackgroundSync: backgroundSyncMutation.mutate, // 동기화 가능 여부 canSync: !!user?.id && syncStatusQuery.data?.canSync, // 쿼리 제어 refetchSyncStatus: syncStatusQuery.refetch, refetchLastSyncTime: lastSyncQuery.refetch, }; }; /** * 동기화 설정 관리 훅 */ export const useSyncSettings = () => { const queryClient = useQueryClient(); // 자동 동기화 간격 설정 (localStorage 기반) const setAutoSyncInterval = (intervalMinutes: number) => { localStorage.setItem("auto-sync-interval", intervalMinutes.toString()); // 관련 쿼리 무효화 queryClient.invalidateQueries({ queryKey: ["auto-sync"], }); syncLogger.info("자동 동기화 간격 설정됨", { intervalMinutes }); }; const getAutoSyncInterval = (): number => { const stored = localStorage.getItem("auto-sync-interval"); return stored ? parseInt(stored, 10) : 5; // 기본값 5분 }; return { setAutoSyncInterval, getAutoSyncInterval, currentInterval: getAutoSyncInterval(), }; };