Refactor SyncSettings component
Refactor SyncSettings component into smaller components and extract logic into a separate hook.
This commit is contained in:
@@ -1,180 +1,25 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { CloudUpload, RefreshCw, AlertCircle } from "lucide-react";
|
||||
import { isSyncEnabled, setSyncEnabled, syncAllData, getLastSyncTime, trySyncAllData, SyncResult } from "@/utils/syncUtils";
|
||||
import { toast } from "@/hooks/useToast.wrapper";
|
||||
import { useAuth } from "@/contexts/auth";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { CloudUpload } from "lucide-react";
|
||||
import { useSyncSettings } from '@/hooks/useSyncSettings';
|
||||
import SyncStatus from '@/components/sync/SyncStatus';
|
||||
import SyncExplanation from '@/components/sync/SyncExplanation';
|
||||
|
||||
const SyncSettings = () => {
|
||||
const [enabled, setEnabled] = useState(isSyncEnabled());
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [lastSync, setLastSync] = useState<string | null>(getLastSyncTime());
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 사용자 로그인 상태 변경 감지
|
||||
useEffect(() => {
|
||||
// 사용자 로그인 상태에 따라 동기화 설정 업데이트
|
||||
const updateSyncState = () => {
|
||||
if (!user && isSyncEnabled()) {
|
||||
// 사용자가 로그아웃했고 동기화가 활성화되어 있으면 비활성화
|
||||
setSyncEnabled(false);
|
||||
setEnabled(false);
|
||||
setLastSync(null);
|
||||
console.log('로그아웃으로 인해 동기화 설정이 비활성화되었습니다.');
|
||||
}
|
||||
|
||||
// 동기화 상태 업데이트
|
||||
setEnabled(isSyncEnabled());
|
||||
setLastSync(getLastSyncTime());
|
||||
};
|
||||
|
||||
// 초기 호출
|
||||
updateSyncState();
|
||||
|
||||
// 인증 상태 변경 이벤트 리스너
|
||||
window.addEventListener('auth-state-changed', updateSyncState);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('auth-state-changed', updateSyncState);
|
||||
};
|
||||
}, [user]);
|
||||
|
||||
// 마지막 동기화 시간 정기적으로 업데이트
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
setLastSync(getLastSyncTime());
|
||||
}, 10000); // 10초마다 업데이트
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
const handleSyncToggle = async (checked: boolean) => {
|
||||
if (!user && checked) {
|
||||
toast({
|
||||
title: "로그인 필요",
|
||||
description: "데이터 동기화를 위해 로그인이 필요합니다.",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setEnabled(checked);
|
||||
setSyncEnabled(checked);
|
||||
|
||||
if (checked && user) {
|
||||
// 동기화 활성화 시 즉시 동기화 실행
|
||||
try {
|
||||
setSyncing(true);
|
||||
// 안전한 동기화 함수 사용
|
||||
const result = await trySyncAllData(user.id);
|
||||
|
||||
if (result.success) {
|
||||
if (result.partial) {
|
||||
toast({
|
||||
title: "동기화 일부 완료",
|
||||
description: "일부 데이터만 동기화되었습니다. 다시 시도해보세요.",
|
||||
variant: "destructive"
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: "동기화 설정 완료",
|
||||
description: "모든 데이터가 클라우드에 동기화되었습니다.",
|
||||
});
|
||||
}
|
||||
setLastSync(getLastSyncTime());
|
||||
} else {
|
||||
toast({
|
||||
title: "동기화 일부 완료",
|
||||
description: "일부 데이터가 동기화되지 않았습니다. 나중에 다시 시도해주세요.",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: "동기화 오류",
|
||||
description: "동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
|
||||
variant: "destructive"
|
||||
});
|
||||
// 실패 시 동기화 비활성화
|
||||
setEnabled(false);
|
||||
setSyncEnabled(false);
|
||||
} finally {
|
||||
setSyncing(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleManualSync = async () => {
|
||||
if (!user) {
|
||||
toast({
|
||||
title: "로그인 필요",
|
||||
description: "데이터 동기화를 위해 로그인이 필요합니다.",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setSyncing(true);
|
||||
// 안전한 동기화 함수 사용
|
||||
const result = await trySyncAllData(user.id);
|
||||
|
||||
if (result.success) {
|
||||
if (result.downloadSuccess && result.uploadSuccess) {
|
||||
toast({
|
||||
title: "동기화 완료",
|
||||
description: "모든 데이터가 클라우드에 동기화되었습니다.",
|
||||
});
|
||||
} else if (result.downloadSuccess) {
|
||||
toast({
|
||||
title: "다운로드만 성공",
|
||||
description: "서버 데이터를 가져왔지만, 업로드에 실패했습니다.",
|
||||
variant: "destructive"
|
||||
});
|
||||
} else if (result.uploadSuccess) {
|
||||
toast({
|
||||
title: "업로드만 성공",
|
||||
description: "로컬 데이터를 업로드했지만, 다운로드에 실패했습니다.",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
setLastSync(getLastSyncTime());
|
||||
} else {
|
||||
toast({
|
||||
title: "일부 동기화 실패",
|
||||
description: "일부 데이터 동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('동기화 오류:', error);
|
||||
toast({
|
||||
title: "동기화 오류",
|
||||
description: "동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
|
||||
variant: "destructive"
|
||||
});
|
||||
} finally {
|
||||
setSyncing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatLastSyncTime = () => {
|
||||
if (!lastSync) return "아직 동기화된 적 없음";
|
||||
|
||||
if (lastSync === '부분-다운로드') return "부분 동기화 (다운로드만)";
|
||||
|
||||
const date = new Date(lastSync);
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
||||
};
|
||||
const {
|
||||
enabled,
|
||||
syncing,
|
||||
user,
|
||||
lastSync,
|
||||
handleSyncToggle,
|
||||
handleManualSync
|
||||
} = useSyncSettings();
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* 동기화 토글 컨트롤 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -196,45 +41,17 @@ const SyncSettings = () => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{enabled && (
|
||||
<div className="space-y-3">
|
||||
{user ? (
|
||||
<>
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-muted-foreground">마지막 동기화: {formatLastSyncTime()}</span>
|
||||
<button
|
||||
onClick={handleManualSync}
|
||||
disabled={syncing}
|
||||
className="neuro-button py-1 px-3 flex items-center gap-1 bg-neuro-income text-white hover:bg-neuro-income/90"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${syncing ? 'animate-spin' : ''}`} />
|
||||
<span>{syncing ? '동기화 중...' : '지금 동기화'}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Alert className="bg-amber-50 text-amber-800 border-amber-200">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>동기화 작동 방식</AlertTitle>
|
||||
<AlertDescription className="text-sm">
|
||||
이 기능은 양방향 동기화입니다. 로그인 후 동기화를 켜면 서버 데이터와 로컬 데이터가 병합됩니다.
|
||||
데이터 초기화 후에도 동기화 버튼을 누르면 서버에 저장된 데이터를 다시 불러옵니다.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</>
|
||||
) : (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">로그인이 필요합니다</span>
|
||||
<Button
|
||||
onClick={() => navigate('/login')}
|
||||
size="sm"
|
||||
className="py-1 px-3 bg-neuro-income text-white hover:bg-neuro-income/90"
|
||||
>
|
||||
로그인하여 동기화
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* 동기화 상태 및 동작 */}
|
||||
<SyncStatus
|
||||
enabled={enabled}
|
||||
syncing={syncing}
|
||||
lastSync={lastSync}
|
||||
user={user}
|
||||
onManualSync={handleManualSync}
|
||||
/>
|
||||
|
||||
{/* 동기화 설명 */}
|
||||
<SyncExplanation enabled={enabled} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
25
src/components/sync/SyncExplanation.tsx
Normal file
25
src/components/sync/SyncExplanation.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
interface SyncExplanationProps {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const SyncExplanation: React.FC<SyncExplanationProps> = ({ enabled }) => {
|
||||
if (!enabled) return null;
|
||||
|
||||
return (
|
||||
<Alert className="bg-amber-50 text-amber-800 border-amber-200">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>동기화 작동 방식</AlertTitle>
|
||||
<AlertDescription className="text-sm">
|
||||
이 기능은 양방향 동기화입니다. 로그인 후 동기화를 켜면 서버 데이터와 로컬 데이터가 병합됩니다.
|
||||
데이터 초기화 후에도 동기화 버튼을 누르면 서버에 저장된 데이터를 다시 불러옵니다.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
export default SyncExplanation;
|
||||
56
src/components/sync/SyncStatus.tsx
Normal file
56
src/components/sync/SyncStatus.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
import React from 'react';
|
||||
import { RefreshCw } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface SyncStatusProps {
|
||||
enabled: boolean;
|
||||
syncing: boolean;
|
||||
lastSync: string;
|
||||
user: any; // User 타입 또는 null
|
||||
onManualSync: () => Promise<void>;
|
||||
}
|
||||
|
||||
const SyncStatus: React.FC<SyncStatusProps> = ({
|
||||
enabled,
|
||||
syncing,
|
||||
lastSync,
|
||||
user,
|
||||
onManualSync
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!enabled) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{user ? (
|
||||
<div className="flex justify-between items-center text-sm">
|
||||
<span className="text-muted-foreground">마지막 동기화: {lastSync}</span>
|
||||
<button
|
||||
onClick={onManualSync}
|
||||
disabled={syncing}
|
||||
className="neuro-button py-1 px-3 flex items-center gap-1 bg-neuro-income text-white hover:bg-neuro-income/90"
|
||||
>
|
||||
<RefreshCw className={`h-4 w-4 ${syncing ? 'animate-spin' : ''}`} />
|
||||
<span>{syncing ? '동기화 중...' : '지금 동기화'}</span>
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">로그인이 필요합니다</span>
|
||||
<Button
|
||||
onClick={() => navigate('/login')}
|
||||
size="sm"
|
||||
className="py-1 px-3 bg-neuro-income text-white hover:bg-neuro-income/90"
|
||||
>
|
||||
로그인하여 동기화
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SyncStatus;
|
||||
176
src/hooks/useSyncSettings.ts
Normal file
176
src/hooks/useSyncSettings.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useAuth } from '@/contexts/auth';
|
||||
import { toast } from '@/hooks/useToast.wrapper';
|
||||
import {
|
||||
isSyncEnabled,
|
||||
setSyncEnabled,
|
||||
getLastSyncTime,
|
||||
trySyncAllData,
|
||||
SyncResult
|
||||
} from '@/utils/syncUtils';
|
||||
|
||||
/**
|
||||
* 동기화 설정 관리를 위한 커스텀 훅
|
||||
*/
|
||||
export const useSyncSettings = () => {
|
||||
const [enabled, setEnabled] = useState(isSyncEnabled());
|
||||
const [syncing, setSyncing] = useState(false);
|
||||
const [lastSync, setLastSync] = useState<string | null>(getLastSyncTime());
|
||||
const { user } = useAuth();
|
||||
|
||||
// 사용자 로그인 상태 변경 감지
|
||||
useEffect(() => {
|
||||
// 사용자 로그인 상태에 따라 동기화 설정 업데이트
|
||||
const updateSyncState = () => {
|
||||
if (!user && isSyncEnabled()) {
|
||||
// 사용자가 로그아웃했고 동기화가 활성화되어 있으면 비활성화
|
||||
setSyncEnabled(false);
|
||||
setEnabled(false);
|
||||
setLastSync(null);
|
||||
console.log('로그아웃으로 인해 동기화 설정이 비활성화되었습니다.');
|
||||
}
|
||||
|
||||
// 동기화 상태 업데이트
|
||||
setEnabled(isSyncEnabled());
|
||||
setLastSync(getLastSyncTime());
|
||||
};
|
||||
|
||||
// 초기 호출
|
||||
updateSyncState();
|
||||
|
||||
// 인증 상태 변경 이벤트 리스너
|
||||
window.addEventListener('auth-state-changed', updateSyncState);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('auth-state-changed', updateSyncState);
|
||||
};
|
||||
}, [user]);
|
||||
|
||||
// 마지막 동기화 시간 정기적으로 업데이트
|
||||
useEffect(() => {
|
||||
const intervalId = setInterval(() => {
|
||||
setLastSync(getLastSyncTime());
|
||||
}, 10000); // 10초마다 업데이트
|
||||
|
||||
return () => clearInterval(intervalId);
|
||||
}, []);
|
||||
|
||||
// 동기화 토글 핸들러
|
||||
const handleSyncToggle = async (checked: boolean) => {
|
||||
if (!user && checked) {
|
||||
toast({
|
||||
title: "로그인 필요",
|
||||
description: "데이터 동기화를 위해 로그인이 필요합니다.",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setEnabled(checked);
|
||||
setSyncEnabled(checked);
|
||||
|
||||
if (checked && user) {
|
||||
// 동기화 활성화 시 즉시 동기화 실행
|
||||
await performSync();
|
||||
}
|
||||
};
|
||||
|
||||
// 수동 동기화 핸들러
|
||||
const handleManualSync = async () => {
|
||||
if (!user) {
|
||||
toast({
|
||||
title: "로그인 필요",
|
||||
description: "데이터 동기화를 위해 로그인이 필요합니다.",
|
||||
variant: "destructive"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await performSync();
|
||||
};
|
||||
|
||||
// 실제 동기화 수행 함수
|
||||
const performSync = async () => {
|
||||
if (!user) return;
|
||||
|
||||
try {
|
||||
setSyncing(true);
|
||||
// 안전한 동기화 함수 사용
|
||||
const result = await trySyncAllData(user.id);
|
||||
|
||||
handleSyncResult(result);
|
||||
setLastSync(getLastSyncTime());
|
||||
} catch (error) {
|
||||
console.error('동기화 오류:', error);
|
||||
toast({
|
||||
title: "동기화 오류",
|
||||
description: "동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
|
||||
variant: "destructive"
|
||||
});
|
||||
|
||||
// 심각한 오류 발생 시 동기화 비활성화
|
||||
if (!enabled) {
|
||||
setEnabled(false);
|
||||
setSyncEnabled(false);
|
||||
}
|
||||
} finally {
|
||||
setSyncing(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 동기화 결과 처리 함수
|
||||
const handleSyncResult = (result: SyncResult) => {
|
||||
if (result.success) {
|
||||
if (result.downloadSuccess && result.uploadSuccess) {
|
||||
toast({
|
||||
title: "동기화 완료",
|
||||
description: "모든 데이터가 클라우드에 동기화되었습니다.",
|
||||
});
|
||||
} else if (result.downloadSuccess) {
|
||||
toast({
|
||||
title: "다운로드만 성공",
|
||||
description: "서버 데이터를 가져왔지만, 업로드에 실패했습니다.",
|
||||
variant: "destructive"
|
||||
});
|
||||
} else if (result.uploadSuccess) {
|
||||
toast({
|
||||
title: "업로드만 성공",
|
||||
description: "로컬 데이터를 업로드했지만, 다운로드에 실패했습니다.",
|
||||
variant: "destructive"
|
||||
});
|
||||
} else if (result.partial) {
|
||||
toast({
|
||||
title: "동기화 일부 완료",
|
||||
description: "일부 데이터만 동기화되었습니다. 다시 시도해보세요.",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
toast({
|
||||
title: "일부 동기화 실패",
|
||||
description: "일부 데이터 동기화 중 문제가 발생했습니다. 다시 시도해주세요.",
|
||||
variant: "destructive"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 마지막 동기화 시간 포맷팅
|
||||
const formatLastSyncTime = () => {
|
||||
if (!lastSync) return "아직 동기화된 적 없음";
|
||||
|
||||
if (lastSync === '부분-다운로드') return "부분 동기화 (다운로드만)";
|
||||
|
||||
const date = new Date(lastSync);
|
||||
return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
||||
};
|
||||
|
||||
return {
|
||||
enabled,
|
||||
syncing,
|
||||
user,
|
||||
lastSync: formatLastSyncTime(),
|
||||
handleSyncToggle,
|
||||
handleManualSync,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user