import React, { useEffect, useState } from 'react'; import { inboxApi } from '../../api.js'; import type { SyncStatusSnapshot } from '@shared/types'; import { ConflictModal } from '../ConflictModal.js'; import { SyncHelpModal, type SyncHelpAnchor } from '../SyncHelpModal.js'; export function SyncSection(): React.ReactElement { const [url, setUrl] = useState(''); const [draftUrl, setDraftUrl] = useState(''); const [autoEnabled, setAutoEnabled] = useState(true); const [intervalMin, setIntervalMin] = useState(30); const [status, setStatus] = useState(null); const [busy, setBusy] = useState<'save' | 'test' | 'sync' | null>(null); const [feedback, setFeedback] = useState(null); const [showConflict, setShowConflict] = useState(false); const [showHelp, setShowHelp] = useState<{ open: boolean; anchor?: SyncHelpAnchor }>({ open: false }); useEffect(() => { void (async () => { const s = await inboxApi.getSettings(); const u = s.sync_repo_url ?? ''; setUrl(u); setDraftUrl(u); setAutoEnabled(s.sync_auto_enabled ?? true); setIntervalMin(s.sync_interval_min ?? 30); setStatus(await inboxApi.getSyncStatus()); })(); }, []); async function onSaveUrl() { setBusy('save'); setFeedback(null); const r = await inboxApi.configureSync(draftUrl.trim() === '' ? null : draftUrl.trim()); setBusy(null); if (r.ok) { setUrl(draftUrl.trim()); setFeedback('저장되었습니다'); } else { setFeedback(`저장 실패: ${r.reason}`); } } async function onTestConnection() { setBusy('test'); setFeedback(null); const r = await inboxApi.testSyncConnection(); setBusy(null); setFeedback(r.ok ? '연결 성공' : `연결 실패: ${r.reason}`); } async function onToggleAuto(next: boolean) { await inboxApi.setSyncAutoEnabled(next); setAutoEnabled(next); } async function onChangeInterval(value: number) { if (!Number.isInteger(value) || value < 5) return; const r = await inboxApi.setSyncIntervalMin(value); if (r.ok) setIntervalMin(value); } const conflictCount = status?.lastResult?.conflicts?.length ?? 0; return (

동기화 저장소

setDraftUrl(e.target.value)} style={{ flex: 1, fontSize: 12, padding: '4px 8px', border: '1px solid #ccc', borderRadius: 4 }} />
{feedback !== null && (
{feedback}
)} {url.trim() !== '' && ( <>
마지막 sync: {status?.lastAt ?? '없음'} {status?.lastResult?.ok === false && status?.lastResult?.reason !== 'conflict' && ( ({status.lastResult.reason}) )}
{conflictCount > 0 && (
)} {showConflict && ( setShowConflict(false)} onResolved={async () => { setStatus(await inboxApi.getSyncStatus()); }} onOpenHelp={(anchor) => setShowHelp({ open: true, anchor })} /> )} )} {showHelp.open && ( setShowHelp({ open: false })} initialAnchor={showHelp.anchor} /> )}
); } function btnStyle(): React.CSSProperties { return { background: '#0a4b80', color: '#fff', border: 'none', cursor: 'pointer', fontSize: 12, padding: '4px 10px', borderRadius: 4 }; }