diff --git a/backend/src/services/album.js b/backend/src/services/album.js index f755a75..e42338d 100644 --- a/backend/src/services/album.js +++ b/backend/src/services/album.js @@ -181,12 +181,13 @@ async function insertTracks(connection, albumId, tracks) { track.composer || null, track.arranger || null, track.lyrics || null, - track.music_video_url || null, + track.video_url || null, + track.video_type || null, ]); await connection.query( `INSERT INTO album_tracks - (album_id, track_number, title, duration, is_title_track, lyricist, composer, arranger, lyrics, music_video_url) + (album_id, track_number, title, duration, is_title_track, lyricist, composer, arranger, lyrics, video_url, video_type) VALUES ?`, [values] ); diff --git a/frontend/src/components/pc/admin/album/TrackItem.jsx b/frontend/src/components/pc/admin/album/TrackItem.jsx index 7e4c2a1..b7a85c8 100644 --- a/frontend/src/components/pc/admin/album/TrackItem.jsx +++ b/frontend/src/components/pc/admin/album/TrackItem.jsx @@ -4,6 +4,13 @@ import { memo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Trash2, Star, ChevronDown } from 'lucide-react'; +import { CustomSelect } from '../common'; + +const VIDEO_TYPE_OPTIONS = [ + { value: '', label: '선택' }, + { value: 'music_video', label: '뮤직비디오' }, + { value: 'special', label: '스페셜 영상' }, +]; /** * @param {Object} props @@ -120,16 +127,26 @@ const TrackItem = memo(function TrackItem({ track, index, onUpdate, onRemove }) - {/* MV URL */} + {/* 비디오 URL */}
- - onUpdate(index, 'music_video_url', e.target.value)} - className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" - placeholder="https://youtube.com/watch?v=..." - /> + +
+ onUpdate(index, 'video_url', e.target.value)} + className="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent" + placeholder="https://youtube.com/watch?v=..." + /> + onUpdate(index, 'video_type', value)} + options={VIDEO_TYPE_OPTIONS} + placeholder="선택" + size="sm" + className="w-32" + /> +
{/* 가사 */} diff --git a/frontend/src/components/pc/admin/common/CustomSelect.jsx b/frontend/src/components/pc/admin/common/CustomSelect.jsx index a32001c..29f6b97 100644 --- a/frontend/src/components/pc/admin/common/CustomSelect.jsx +++ b/frontend/src/components/pc/admin/common/CustomSelect.jsx @@ -9,10 +9,12 @@ import { ChevronDown } from 'lucide-react'; * @param {Object} props * @param {string} props.value - 선택된 값 * @param {Function} props.onChange - 값 변경 핸들러 - * @param {string[]} props.options - 옵션 목록 + * @param {Array} props.options - 옵션 목록 (문자열 또는 {value, label} 객체) * @param {string} props.placeholder - 플레이스홀더 + * @param {string} props.className - 추가 클래스명 + * @param {string} props.size - 크기 ('sm' | 'md') */ -function CustomSelect({ value, onChange, options, placeholder }) { +function CustomSelect({ value, onChange, options, placeholder, className = '', size = 'md' }) { const [isOpen, setIsOpen] = useState(false); const ref = useRef(null); @@ -26,17 +28,29 @@ function CustomSelect({ value, onChange, options, placeholder }) { return () => document.removeEventListener('mousedown', handleClickOutside); }, []); + // 옵션을 {value, label} 형태로 정규화 + const normalizedOptions = options.map((opt) => + typeof opt === 'string' ? { value: opt, label: opt } : opt + ); + + // 현재 선택된 옵션의 라벨 찾기 + const selectedLabel = normalizedOptions.find((opt) => opt.value === value)?.label; + + const sizeClasses = size === 'sm' ? 'px-3 py-2 text-sm' : 'px-4 py-2.5'; + return ( -
+
@@ -49,19 +63,19 @@ function CustomSelect({ value, onChange, options, placeholder }) { transition={{ duration: 0.15 }} className="absolute z-50 w-full mt-2 bg-white border border-gray-200 rounded-xl shadow-lg overflow-hidden" > - {options.map((option) => ( + {normalizedOptions.map((option) => ( ))} diff --git a/frontend/src/pages/mobile/album/TrackDetail.jsx b/frontend/src/pages/mobile/album/TrackDetail.jsx index 888e101..2e76f45 100644 --- a/frontend/src/pages/mobile/album/TrackDetail.jsx +++ b/frontend/src/pages/mobile/album/TrackDetail.jsx @@ -37,7 +37,8 @@ function MobileTrackDetail() { enabled: !!albumName && !!trackTitle, }); - const youtubeVideoId = useMemo(() => getYoutubeVideoId(track?.music_video_url), [track?.music_video_url]); + const youtubeVideoId = useMemo(() => getYoutubeVideoId(track?.video_url), [track?.video_url]); + const videoLabel = track?.video_type === 'special' ? '스페셜 영상' : '뮤직비디오'; // 가사 펼침 상태 const [showFullLyrics, setShowFullLyrics] = useState(false); @@ -140,12 +141,12 @@ function MobileTrackDetail() { >

- 뮤직비디오 + {videoLabel}