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 */}
{/* 가사 */}
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}