import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { useQuery } from '@tanstack/react-query'; import { X, Download, ChevronRight, Info, Users, Tag } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; import { Swiper, SwiperSlide } from 'swiper/react'; import { Virtual } from 'swiper/modules'; import 'swiper/css'; import { getAlbumByName } from '@/api/albums'; import { LightboxIndicator } from '@/components/common'; /** * Mobile 앨범 갤러리 페이지 */ function MobileAlbumGallery() { const { name } = useParams(); const navigate = useNavigate(); const [selectedIndex, setSelectedIndex] = useState(null); const [showInfo, setShowInfo] = useState(false); const swiperRef = useRef(null); // 앨범 데이터 로드 const { data: album, isLoading: loading } = useQuery({ queryKey: ['album', name], queryFn: () => getAlbumByName(name), enabled: !!name, }); // 앨범 데이터에서 사진 목록 추출 const photos = useMemo(() => { if (!album?.conceptPhotos) return []; const allPhotos = []; Object.entries(album.conceptPhotos).forEach(([concept, conceptPhotos]) => { conceptPhotos.forEach((p) => allPhotos.push({ ...p, concept: concept !== 'Default' ? concept : null, }) ); }); return allPhotos; }, [album]); // 라이트박스 열기 const openLightbox = useCallback((index) => { setSelectedIndex(index); window.history.pushState({ lightbox: true }, ''); }, []); // 정보 시트 열기 const openInfo = useCallback(() => { setShowInfo(true); window.history.pushState({ infoSheet: true }, ''); }, []); // 뒤로가기 처리 useEffect(() => { const handlePopState = () => { if (showInfo) { setShowInfo(false); } else if (selectedIndex !== null) { setSelectedIndex(null); } }; window.addEventListener('popstate', handlePopState); return () => window.removeEventListener('popstate', handlePopState); }, [showInfo, selectedIndex]); // 이미지 다운로드 const downloadImage = useCallback(async () => { const photo = photos[selectedIndex]; if (!photo) return; try { const response = await fetch(photo.original_url); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = `fromis9_${album?.title || 'photo'}_${String(selectedIndex + 1).padStart(2, '0')}.webp`; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); } catch (error) { console.error('다운로드 오류:', error); } }, [photos, selectedIndex, album?.title]); // 바디 스크롤 방지 useEffect(() => { if (selectedIndex !== null) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } return () => { document.body.style.overflow = ''; }; }, [selectedIndex]); // 사진을 2열로 균등 분배 (높이 기반) const distributePhotos = () => { const leftColumn = []; const rightColumn = []; let leftHeight = 0; let rightHeight = 0; photos.forEach((photo, index) => { const aspectRatio = photo.height && photo.width ? photo.height / photo.width : 1; if (leftHeight <= rightHeight) { leftColumn.push({ ...photo, originalIndex: index }); leftHeight += aspectRatio; } else { rightColumn.push({ ...photo, originalIndex: index }); rightHeight += aspectRatio; } }); return { leftColumn, rightColumn }; }; const { leftColumn, rightColumn } = distributePhotos(); // 현재 사진 정보 const currentPhoto = selectedIndex !== null ? photos[selectedIndex] : null; const hasInfo = currentPhoto?.concept || currentPhoto?.members; // 정보 시트 드래그 핸들러 const handleInfoDragEnd = (_, info) => { if (info.offset.y > 100 || info.velocity.y > 300) { window.history.back(); } }; if (loading) { return (
); } return ( <>
{/* 앨범 헤더 카드 */}
navigate(-1)} > {album?.cover_thumb_url && ( {album.title} )}

컨셉 포토

{album?.title}

{photos.length}장의 사진

{/* 2열 그리드 */}
{leftColumn.map((photo) => ( openLightbox(photo.originalIndex)} className="cursor-pointer overflow-hidden rounded-xl bg-gray-100" > ))}
{rightColumn.map((photo) => ( openLightbox(photo.originalIndex)} className="cursor-pointer overflow-hidden rounded-xl bg-gray-100" > ))}
{/* 풀스크린 라이트박스 */} {selectedIndex !== null && ( {/* 상단 헤더 */}
{selectedIndex + 1} / {photos.length}
{hasInfo && ( )}
{/* Swiper */} { swiperRef.current = swiper; }} onSlideChange={(swiper) => setSelectedIndex(swiper.activeIndex)} className="w-full h-full" spaceBetween={0} slidesPerView={1} resistance={true} resistanceRatio={0.5} > {photos.map((photo, index) => (
))}
{/* 모바일용 인디케이터 */} swiperRef.current?.slideTo(i)} width={120} /> {/* 정보 바텀시트 */} {showInfo && hasInfo && ( window.history.back()} > e.stopPropagation()} > {/* 드래그 핸들 */}
{/* 정보 내용 */}

사진 정보

{currentPhoto?.members && (

멤버

{currentPhoto.members}

)} {currentPhoto?.concept && (

컨셉

{currentPhoto.concept}

)}
)} )} ); } export default MobileAlbumGallery;