diff --git a/src/pages/travel/AlbumDetail.jsx b/src/pages/travel/AlbumDetail.jsx index 18975a0..cd5cac5 100644 --- a/src/pages/travel/AlbumDetail.jsx +++ b/src/pages/travel/AlbumDetail.jsx @@ -29,6 +29,7 @@ export default function AlbumDetail({ onClose, onLoadMore, onReload, + onCoverChange, }) { const isMobile = useIsMobile(); @@ -209,6 +210,7 @@ export default function AlbumDetail({ onClose={handleLightboxClose} onNavigate={handleLightboxNavigate} onLoadMore={onLoadMore} + onCoverChange={onCoverChange} /> )} diff --git a/src/pages/travel/HeroLightbox.jsx b/src/pages/travel/HeroLightbox.jsx index 89349e2..469ddaa 100644 --- a/src/pages/travel/HeroLightbox.jsx +++ b/src/pages/travel/HeroLightbox.jsx @@ -43,6 +43,7 @@ export default function HeroLightbox({ onClose, onNavigate, onLoadMore, + onCoverChange, }) { const isMobile = useIsMobile(); const [phase, setPhase] = useState('enter'); @@ -163,12 +164,13 @@ export default function HeroLightbox({ }); if (!res.ok) throw new Error(`${res.status}`); setCoverStatus('done'); + onCoverChange?.(); } catch { setCoverStatus('error'); } if (coverTimerRef.current) clearTimeout(coverTimerRef.current); coverTimerRef.current = setTimeout(() => setCoverStatus(null), 2000); - }, [selectedIndex, photos, albumName, coverStatus]); + }, [selectedIndex, photos, albumName, coverStatus, onCoverChange]); /* — Current photo — */ const photo = photos[selectedIndex]; diff --git a/src/pages/travel/Travel.jsx b/src/pages/travel/Travel.jsx index bed72d9..91d72bb 100644 --- a/src/pages/travel/Travel.jsx +++ b/src/pages/travel/Travel.jsx @@ -25,6 +25,7 @@ const Travel = () => { loadMorePhotos, reloadAlbumPhotos, getFilteredAlbums, + refreshAlbums, } = useTravelData(); /* ── Local state ──────────────────────────── */ @@ -220,6 +221,7 @@ const Travel = () => { onClose={handleCloseAlbum} onLoadMore={handleLoadMore} onReload={handleReload} + onCoverChange={refreshAlbums} /> )} diff --git a/src/pages/travel/useTravelData.js b/src/pages/travel/useTravelData.js index ff71b4c..9e933d6 100644 --- a/src/pages/travel/useTravelData.js +++ b/src/pages/travel/useTravelData.js @@ -93,80 +93,36 @@ const useTravelData = () => { return () => controller.abort(); }, []); - /* ── Build album list when regions arrive ── */ + /* ── Build album list from /api/travel/albums ── */ + const fetchAlbums = useCallback(async (signal) => { + setLoadingAlbums(true); + try { + const res = await fetch('/api/travel/albums', { signal }); + if (!res.ok) throw new Error(`앨범 로딩 실패 (${res.status})`); + const rows = await res.json(); + const builtAlbums = rows.map((r) => ({ + id: `${r.region}::${r.album}`, + name: r.album, + region: r.region, + regionName: r.regionName || r.region, + photoCount: r.count ?? 0, + coverThumb: r.cover_thumb || '', + })); + setAlbums(builtAlbums); + } catch (err) { + if (err?.name !== 'AbortError') { + setError(err?.message ?? String(err)); + } + } finally { + setLoadingAlbums(false); + } + }, []); + useEffect(() => { - if (!regions?.features?.length) return; - const controller = new AbortController(); - (async () => { - setLoadingAlbums(true); - const builtAlbums = []; - - for (const feature of regions.features) { - if (controller.signal.aborted) break; - const regionId = feature?.properties?.id; - const regionName = feature?.properties?.name || regionId || ''; - if (!regionId) continue; - - // Use cached album metadata if fresh - const cached = albumCacheRef.current.get(regionId); - if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) { - builtAlbums.push(...cached.albums); - continue; - } - - try { - const res = await fetch( - `/api/travel/photos?region=${encodeURIComponent(regionId)}&page=1&size=${PAGE_SIZE}`, - { signal: controller.signal } - ); - if (!res.ok) continue; // skip failed regions silently - const json = await res.json(); - const { normalized, matchedAlbums } = parsePhotoResponse(json); - - const regionAlbums = matchedAlbums.map((ma) => { - // Find first photo that belongs to this album for coverThumb - const cover = normalized.find((p) => p.album === ma.album); - return { - id: `${regionId}::${ma.album}`, - name: ma.album, - region: regionId, - regionName, - photoCount: ma.count ?? 0, - coverThumb: cover?.src || '', - }; - }); - - // If API returned no matched_albums, create a single implicit album - if (regionAlbums.length === 0 && normalized.length > 0) { - regionAlbums.push({ - id: `${regionId}::`, - name: regionName, - region: regionId, - regionName, - photoCount: normalized.length, - coverThumb: normalized[0]?.src || '', - }); - } - - albumCacheRef.current.set(regionId, { - timestamp: Date.now(), - albums: regionAlbums, - }); - builtAlbums.push(...regionAlbums); - } catch (err) { - if (err?.name === 'AbortError') break; - // Non-fatal — continue with other regions - } - } - - if (!controller.signal.aborted) { - setAlbums(builtAlbums); - setLoadingAlbums(false); - } - })(); + fetchAlbums(controller.signal); return () => controller.abort(); - }, [regions]); + }, [fetchAlbums]); /* ── loadAlbumPhotos — initial load ────── */ const loadAlbumPhotos = useCallback(async (regionId, albumName) => { @@ -362,6 +318,7 @@ const useTravelData = () => { loadMorePhotos, reloadAlbumPhotos, getFilteredAlbums, + refreshAlbums: () => fetchAlbums(), }; };