From 0a77765a6dca2a73dbf9a8907447047a0399025c Mon Sep 17 00:00:00 2001 From: caadiq Date: Sat, 3 Jan 2026 10:01:34 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=95=A8=EB=B2=94=20=EC=83=81=EC=84=B8?= =?UTF-8?q?=20=EB=A0=88=EC=9D=B4=EC=95=84=EC=9B=83=20=EA=B0=9C=EC=84=A0,?= =?UTF-8?q?=20=EA=B4=80=EB=A6=AC=20=ED=99=94=EB=A9=B4=20=EC=8D=B8=EB=84=A4?= =?UTF-8?q?=EC=9D=BC=20=ED=99=95=EB=8C=80=20=EB=B0=8F=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 앨범 소개를 다이얼로그로 분리 (점3개 메뉴) - 수록곡 전체 너비로 표시 - 관리 화면 썸네일 180px로 확대 - 메타 영역 고정 높이 (200px) - lazy loading 및 애니메이션 최적화 --- frontend/package-lock.json | 13 +- frontend/package.json | 3 +- frontend/src/pages/pc/AlbumDetail.jsx | 186 +++++++++++------- .../src/pages/pc/admin/AdminAlbumPhotos.jsx | 21 +- 4 files changed, 143 insertions(+), 80 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f797e90..dfd9f53 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -14,7 +14,8 @@ "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", "react-photo-album": "^3.4.0", - "react-router-dom": "^6.22.3" + "react-router-dom": "^6.22.3", + "react-window": "^2.2.3" }, "devDependencies": { "@types/react": "^18.3.3", @@ -2310,6 +2311,16 @@ "react-dom": ">=16.8" } }, + "node_modules/react-window": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-2.2.3.tgz", + "integrity": "sha512-gTRqQYC8ojbiXyd9duYFiSn2TJw0ROXCgYjenOvNKITWzK0m0eCvkUsEUM08xvydkMh7ncp+LE0uS3DeNGZxnQ==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4ea98f9..d430244 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,7 +15,8 @@ "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", "react-photo-album": "^3.4.0", - "react-router-dom": "^6.22.3" + "react-router-dom": "^6.22.3", + "react-window": "^2.2.3" }, "devDependencies": { "@types/react": "^18.3.3", diff --git a/frontend/src/pages/pc/AlbumDetail.jsx b/frontend/src/pages/pc/AlbumDetail.jsx index 878f0bb..babfc43 100644 --- a/frontend/src/pages/pc/AlbumDetail.jsx +++ b/frontend/src/pages/pc/AlbumDetail.jsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { motion, AnimatePresence } from 'framer-motion'; -import { Calendar, Music2, Clock, X, ChevronLeft, ChevronRight, Download } from 'lucide-react'; +import { Calendar, Music2, Clock, X, ChevronLeft, ChevronRight, Download, MoreVertical, FileText } from 'lucide-react'; function AlbumDetail() { const { name } = useParams(); @@ -11,6 +11,8 @@ function AlbumDetail() { const [lightbox, setLightbox] = useState({ open: false, images: [], index: 0 }); const [slideDirection, setSlideDirection] = useState(0); const [imageLoaded, setImageLoaded] = useState(false); + const [showDescriptionModal, setShowDescriptionModal] = useState(false); + const [showMenu, setShowMenu] = useState(false); // 라이트박스 네비게이션 함수 const goToPrev = useCallback(() => { @@ -202,7 +204,7 @@ function AlbumDetail() { initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} transition={{ duration: 0.4 }} - className="w-80 h-80 flex-shrink-0 rounded-2xl overflow-hidden shadow-2xl" + className="w-80 h-80 flex-shrink-0 rounded-2xl overflow-hidden shadow-lg" >
- - {album.album_type} - +
+ + {album.album_type} + + {/* 점3개 메뉴 - 소개글이 있을 때만 */} + {album.description && ( +
+ + {showMenu && ( + <> +
setShowMenu(false)} + /> +
+ +
+ + )} +
+ )} +

{album.title}

@@ -268,71 +303,48 @@ function AlbumDetail() {
- {/* 2열 그리드: 소개글 + 트랙 리스트 */} -
- {/* 소개글 */} - {album.description && ( - -

앨범 소개

-
-
-

- {album.description} -

+ {/* 수록곡 리스트 */} + +

수록곡

+
+ {album.tracks?.map((track, index) => ( +
+ {/* 트랙 번호 */} +
+ + {String(track.track_number).padStart(2, '0')} + +
+ + {/* 트랙 정보 */} +
+
+

{track.title}

+ {track.is_title_track === 1 && ( + + TITLE + + )} +
+
+ + {/* 재생 시간 */} +
+ {track.duration || '-'}
- - )} - - {/* 트랙 리스트 */} - -

수록곡

-
- {album.tracks?.map((track, index) => ( -
- {/* 트랙 번호 */} -
- - {String(track.track_number).padStart(2, '0')} - -
- - {/* 트랙 정보 */} -
-
-

{track.title}

- {track.is_title_track === 1 && ( - - TITLE - - )} -
-
- - {/* 재생 시간 */} -
- {track.duration || '-'} -
-
- ))} -
-
-
+ ))} +
+ {/* 컨셉 포토 섹션 */} {album.conceptPhotos && Object.keys(album.conceptPhotos).length > 0 && ( @@ -483,6 +495,44 @@ function AlbumDetail() { )} + + {/* 앨범 소개 다이얼로그 */} + + {showDescriptionModal && album?.description && ( + setShowDescriptionModal(false)} + > + e.stopPropagation()} + > + {/* 헤더 */} +
+

앨범 소개

+ +
+ {/* 내용 */} +
+

+ {album.description} +

+
+
+
+ )} +
); } diff --git a/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx b/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx index 1919c70..4d10589 100644 --- a/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx +++ b/frontend/src/pages/pc/admin/AdminAlbumPhotos.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from 'react'; +import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { useNavigate, Link, useParams } from 'react-router-dom'; import { motion, AnimatePresence, Reorder } from 'framer-motion'; import { @@ -973,17 +973,18 @@ function AdminAlbumPhotos() { />
- {/* 썸네일 (작게 축소하여 스크롤 성능 개선) */} + {/* 썸네일 (180px로 확대) */} {file.filename} setPreviewPhoto(file)} /> - {/* 메타 정보 */} -
+ {/* 메타 정보 - 고정 높이 */} +
{/* 파일명 */}

{file.filename}

@@ -1039,9 +1040,7 @@ function AdminAlbumPhotos() { ))} )} - {file.groupType === 'solo' && ( - (한 명만 선택) - )} +
{/* 전 멤버 (다음 줄) */} {file.groupType !== 'group' && members.filter(m => m.is_former).length > 0 && ( @@ -1336,7 +1335,7 @@ function AdminAlbumPhotos() { key={photo.id} initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} - transition={{ duration: 0.2, delay: index * 0.02 }} + transition={{ duration: 0.2, delay: index < 20 ? index * 0.02 : 0 }} className={`relative group aspect-square rounded-lg overflow-hidden cursor-pointer border-2 transition-all duration-200 ${ selectedPhotos.includes(photo.id) ? 'border-primary ring-2 ring-primary/30 scale-[0.98]' @@ -1353,6 +1352,7 @@ function AdminAlbumPhotos() { {`사진 {/* 호버 시 반투명 오버레이 */} @@ -1405,7 +1405,7 @@ function AdminAlbumPhotos() { key={teaser.id} initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} - transition={{ duration: 0.2, delay: index * 0.02 }} + transition={{ duration: 0.2, delay: index < 20 ? index * 0.02 : 0 }} className={`relative group aspect-square rounded-lg overflow-hidden cursor-pointer border-2 transition-all duration-200 ${ selectedPhotos.includes(`teaser-${teaser.id}`) ? 'border-primary ring-2 ring-primary/30 scale-[0.98]' @@ -1423,6 +1423,7 @@ function AdminAlbumPhotos() { {`티저 {/* 호버 시 반투명 오버레이 */}