diff --git a/backend/routes/albums.js b/backend/routes/albums.js index a58440a..f3929dc 100644 --- a/backend/routes/albums.js +++ b/backend/routes/albums.js @@ -62,7 +62,7 @@ async function getAlbumDetails(album) { router.get("/", async (req, res) => { try { const [albums] = await pool.query( - "SELECT id, title, album_type, album_type_short, release_date, cover_original_url, cover_medium_url, cover_thumb_url FROM albums ORDER BY release_date DESC" + "SELECT id, title, folder_name, album_type, album_type_short, release_date, cover_original_url, cover_medium_url, cover_thumb_url FROM albums ORDER BY release_date DESC" ); // 각 앨범에 트랙 정보 추가 diff --git a/frontend/src/pages/mobile/public/AlbumDetail.jsx b/frontend/src/pages/mobile/public/AlbumDetail.jsx index f62105c..b9adc57 100644 --- a/frontend/src/pages/mobile/public/AlbumDetail.jsx +++ b/frontend/src/pages/mobile/public/AlbumDetail.jsx @@ -1,34 +1,100 @@ -import { motion } from 'framer-motion'; -import { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useState, useEffect, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; -import { ArrowLeft, Play } from 'lucide-react'; -import { getAlbums, getAlbumTracks } from '../../../api/public/albums'; +import { ArrowLeft, Play, Calendar, Music2, Clock, X, ChevronLeft, ChevronRight, Download } from 'lucide-react'; +import { getAlbumByName } from '../../../api/public/albums'; +import { formatDate } from '../../../utils/date'; // 모바일 앨범 상세 페이지 function MobileAlbumDetail() { const { name } = useParams(); const navigate = useNavigate(); const [album, setAlbum] = useState(null); - const [tracks, setTracks] = useState([]); const [loading, setLoading] = useState(true); + const [lightbox, setLightbox] = useState({ open: false, images: [], index: 0 }); + const [imageLoaded, setImageLoaded] = useState(false); + + // 라이트박스 네비게이션 + const goToPrev = useCallback(() => { + if (lightbox.images.length <= 1) return; + setImageLoaded(false); + setLightbox(prev => ({ + ...prev, + index: (prev.index - 1 + prev.images.length) % prev.images.length + })); + }, [lightbox.images.length]); + + const goToNext = useCallback(() => { + if (lightbox.images.length <= 1) return; + setImageLoaded(false); + setLightbox(prev => ({ + ...prev, + index: (prev.index + 1) % prev.images.length + })); + }, [lightbox.images.length]); + + const closeLightbox = useCallback(() => { + setLightbox(prev => ({ ...prev, open: false })); + }, []); + + // 이미지 다운로드 + const downloadImage = useCallback(async () => { + const imageUrl = lightbox.images[lightbox.index]; + if (!imageUrl) return; + + try { + const response = await fetch(imageUrl); + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `fromis9_photo_${String(lightbox.index + 1).padStart(2, '0')}.webp`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + } catch (error) { + console.error('다운로드 오류:', error); + } + }, [lightbox.images, lightbox.index]); + + // 라이트박스 body 스크롤 방지 + useEffect(() => { + if (lightbox.open) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + return () => { document.body.style.overflow = ''; }; + }, [lightbox.open]); useEffect(() => { - // 앨범 정보 로드 - getAlbums() + getAlbumByName(name) .then(data => { - const found = data.find(a => a.folder_name === name); - if (found) { - setAlbum(found); - // 트랙 정보 로드 - getAlbumTracks(found.id) - .then(setTracks) - .catch(console.error); - } + setAlbum(data); setLoading(false); }) - .catch(console.error); + .catch(error => { + console.error('앨범 로드 오류:', error); + setLoading(false); + }); }, [name]); + // 총 재생 시간 계산 + const getTotalDuration = () => { + if (!album?.tracks) return ''; + let totalSeconds = 0; + album.tracks.forEach(track => { + if (track.duration) { + const parts = track.duration.split(':'); + totalSeconds += parseInt(parts[0]) * 60 + parseInt(parts[1]); + } + }); + const mins = Math.floor(totalSeconds / 60); + const secs = totalSeconds % 60; + return `${mins}:${String(secs).padStart(2, '0')}`; + }; + if (loading) { return (
티저 포토
++ {track.title} +
+ {track.is_title_track === 1 && ( + + TITLE + + )} ++ {album.description} +
+{album.album_type}
-{album.release_date}
- - -- {album.description} -
-- 타이틀곡: {album.tracks?.find(t => t.is_title_track === 1)?.title || album.tracks?.[0]?.title} -
{/* 앨범 티저 이미지/영상 */}