feat(travel): 사진 동기화 버튼 추가 — POST /api/travel/sync 호출 + 결과 토스트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 01:17:10 +09:00
parent 27dca3df69
commit d6ace70bff
2 changed files with 104 additions and 1 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useRef } from 'react';
import useTravelData from './useTravelData';
import MiniMap, { getRegionAccent } from './MiniMap';
import AlbumCard from './AlbumCard';
@@ -30,6 +30,9 @@ const Travel = () => {
/* ── Local state ──────────────────────────── */
const [selectedAlbum, setSelectedAlbum] = useState(null);
const [albumSourceRect, setAlbumSourceRect] = useState(null);
const [syncing, setSyncing] = useState(false);
const [syncResult, setSyncResult] = useState(null);
const syncTimerRef = useRef(null);
/* ── Computed ──────────────────────────────── */
const regionAccent = getRegionAccent(selectedRegion || '');
@@ -71,6 +74,26 @@ const Travel = () => {
return reloadAlbumPhotos(selectedAlbum.region, selectedAlbum.name);
}, [selectedAlbum, reloadAlbumPhotos]);
const handleSync = useCallback(async () => {
if (syncing) return;
setSyncing(true);
setSyncResult(null);
try {
const res = await fetch('/api/travel/sync', { method: 'POST' });
if (!res.ok) throw new Error(`${res.status}`);
const data = await res.json();
setSyncResult(data);
if (syncTimerRef.current) clearTimeout(syncTimerRef.current);
syncTimerRef.current = setTimeout(() => setSyncResult(null), 8000);
} catch (e) {
setSyncResult({ error: e.message });
if (syncTimerRef.current) clearTimeout(syncTimerRef.current);
syncTimerRef.current = setTimeout(() => setSyncResult(null), 5000);
} finally {
setSyncing(false);
}
}, [syncing]);
/* ── Render ────────────────────────────────── */
return (
<div className="travel" style={{ '--region-accent': regionAccent }}>
@@ -92,6 +115,28 @@ const Travel = () => {
여행에서 포착한 , , 장면들을 필름처럼 현상합니다.
지도에서 지역을 선택하면 해당 앨범이 펼쳐집니다.
</p>
<button
className={`tv-sync-btn${syncing ? ' is-syncing' : ''}`}
onClick={handleSync}
disabled={syncing}
title="사진 폴더 동기화"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden>
<path d="M1.5 8a6.5 6.5 0 0 1 11.25-4.43" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/>
<path d="M14.5 8a6.5 6.5 0 0 1-11.25 4.43" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round"/>
<path d="M12 1.5v2.5h-2.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M4 14.5v-2.5h2.5" stroke="currentColor" strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
{syncing ? '동기화 중…' : '동기화'}
</button>
{syncResult && (
<div className={`tv-sync-toast${syncResult.error ? ' is-error' : ''}`}>
{syncResult.error
? `동기화 실패: ${syncResult.error}`
: `+${syncResult.added} 추가 / ${syncResult.removed} 삭제 / 썸네일 ${syncResult.thumbs_generated}개 (${syncResult.duration_sec}s)`
}
</div>
)}
</div>
{selectedRegion ? (